From 03e25150ec1f30b535c65d62ce199dc2a77d3b76 Mon Sep 17 00:00:00 2001 From: lukemun Date: Wed, 29 Jan 2025 20:55:29 -0500 Subject: [PATCH] AWS - SNS publisher and lambda code Work in progress --- .gitignore | 8 + aws/inventory_update_sns.py | 411 ++ .../certifi-2024.12.14.dist-info/INSTALLER | 1 + .../certifi-2024.12.14.dist-info/LICENSE | 20 + .../certifi-2024.12.14.dist-info/METADATA | 68 + .../certifi-2024.12.14.dist-info/RECORD | 14 + .../certifi-2024.12.14.dist-info/WHEEL | 5 + .../top_level.txt | 1 + aws/lambda_demo/certifi/__init__.py | 4 + aws/lambda_demo/certifi/__main__.py | 12 + aws/lambda_demo/certifi/cacert.pem | 4856 +++++++++++++++++ aws/lambda_demo/certifi/core.py | 114 + aws/lambda_demo/certifi/py.typed | 0 aws/lambda_demo/lambda_demo.zip | Bin 0 -> 1526024 bytes aws/lambda_demo/lambda_function.py | 113 + .../sentry_sdk-2.20.0.dist-info/INSTALLER | 1 + .../sentry_sdk-2.20.0.dist-info/LICENSE | 21 + .../sentry_sdk-2.20.0.dist-info/METADATA | 244 + .../sentry_sdk-2.20.0.dist-info/RECORD | 293 + .../sentry_sdk-2.20.0.dist-info/REQUESTED | 0 .../sentry_sdk-2.20.0.dist-info/WHEEL | 6 + .../entry_points.txt | 2 + .../sentry_sdk-2.20.0.dist-info/top_level.txt | 1 + aws/lambda_demo/sentry_sdk/__init__.py | 57 + aws/lambda_demo/sentry_sdk/_compat.py | 98 + .../sentry_sdk/_init_implementation.py | 84 + aws/lambda_demo/sentry_sdk/_lru_cache.py | 47 + aws/lambda_demo/sentry_sdk/_queue.py | 287 + aws/lambda_demo/sentry_sdk/_types.py | 222 + aws/lambda_demo/sentry_sdk/_werkzeug.py | 98 + aws/lambda_demo/sentry_sdk/ai/__init__.py | 0 aws/lambda_demo/sentry_sdk/ai/monitoring.py | 115 + aws/lambda_demo/sentry_sdk/ai/utils.py | 32 + aws/lambda_demo/sentry_sdk/api.py | 433 ++ aws/lambda_demo/sentry_sdk/attachments.py | 75 + aws/lambda_demo/sentry_sdk/client.py | 941 ++++ aws/lambda_demo/sentry_sdk/consts.py | 584 ++ aws/lambda_demo/sentry_sdk/crons/__init__.py | 10 + aws/lambda_demo/sentry_sdk/crons/api.py | 57 + aws/lambda_demo/sentry_sdk/crons/consts.py | 4 + aws/lambda_demo/sentry_sdk/crons/decorator.py | 135 + aws/lambda_demo/sentry_sdk/debug.py | 41 + aws/lambda_demo/sentry_sdk/envelope.py | 349 ++ aws/lambda_demo/sentry_sdk/feature_flags.py | 42 + aws/lambda_demo/sentry_sdk/hub.py | 739 +++ .../sentry_sdk/integrations/__init__.py | 275 + .../sentry_sdk/integrations/_asgi_common.py | 108 + .../sentry_sdk/integrations/_wsgi_common.py | 264 + .../sentry_sdk/integrations/aiohttp.py | 357 ++ .../sentry_sdk/integrations/anthropic.py | 286 + .../sentry_sdk/integrations/argv.py | 31 + .../sentry_sdk/integrations/ariadne.py | 161 + .../sentry_sdk/integrations/arq.py | 245 + .../sentry_sdk/integrations/asgi.py | 335 ++ .../sentry_sdk/integrations/asyncio.py | 107 + .../sentry_sdk/integrations/asyncpg.py | 208 + .../sentry_sdk/integrations/atexit.py | 57 + .../sentry_sdk/integrations/aws_lambda.py | 496 ++ .../sentry_sdk/integrations/beam.py | 176 + .../sentry_sdk/integrations/boto3.py | 137 + .../sentry_sdk/integrations/bottle.py | 215 + .../integrations/celery/__init__.py | 528 ++ .../sentry_sdk/integrations/celery/beat.py | 293 + .../sentry_sdk/integrations/celery/utils.py | 43 + .../sentry_sdk/integrations/chalice.py | 134 + .../integrations/clickhouse_driver.py | 157 + .../integrations/cloud_resource_context.py | 258 + .../sentry_sdk/integrations/cohere.py | 270 + .../sentry_sdk/integrations/dedupe.py | 42 + .../integrations/django/__init__.py | 747 +++ .../sentry_sdk/integrations/django/asgi.py | 245 + .../sentry_sdk/integrations/django/caching.py | 191 + .../integrations/django/middleware.py | 187 + .../integrations/django/signals_handlers.py | 91 + .../integrations/django/templates.py | 188 + .../integrations/django/transactions.py | 159 + .../sentry_sdk/integrations/django/views.py | 96 + .../sentry_sdk/integrations/dramatiq.py | 168 + .../sentry_sdk/integrations/excepthook.py | 83 + .../sentry_sdk/integrations/executing.py | 67 + .../sentry_sdk/integrations/falcon.py | 272 + .../sentry_sdk/integrations/fastapi.py | 147 + .../sentry_sdk/integrations/flask.py | 263 + .../sentry_sdk/integrations/gcp.py | 234 + .../sentry_sdk/integrations/gnu_backtrace.py | 107 + .../sentry_sdk/integrations/gql.py | 140 + .../sentry_sdk/integrations/graphene.py | 151 + .../sentry_sdk/integrations/grpc/__init__.py | 151 + .../integrations/grpc/aio/__init__.py | 7 + .../integrations/grpc/aio/client.py | 94 + .../integrations/grpc/aio/server.py | 100 + .../sentry_sdk/integrations/grpc/client.py | 92 + .../sentry_sdk/integrations/grpc/consts.py | 1 + .../sentry_sdk/integrations/grpc/server.py | 66 + .../sentry_sdk/integrations/httpx.py | 167 + .../sentry_sdk/integrations/huey.py | 174 + .../integrations/huggingface_hub.py | 175 + .../sentry_sdk/integrations/langchain.py | 465 ++ .../sentry_sdk/integrations/launchdarkly.py | 62 + .../sentry_sdk/integrations/litestar.py | 286 + .../sentry_sdk/integrations/logging.py | 294 + .../sentry_sdk/integrations/loguru.py | 100 + .../sentry_sdk/integrations/modules.py | 29 + .../sentry_sdk/integrations/openai.py | 429 ++ .../sentry_sdk/integrations/openfeature.py | 39 + .../integrations/opentelemetry/__init__.py | 7 + .../integrations/opentelemetry/consts.py | 5 + .../integrations/opentelemetry/integration.py | 58 + .../integrations/opentelemetry/propagator.py | 117 + .../opentelemetry/span_processor.py | 391 ++ .../sentry_sdk/integrations/pure_eval.py | 139 + .../sentry_sdk/integrations/pymongo.py | 214 + .../sentry_sdk/integrations/pyramid.py | 229 + .../sentry_sdk/integrations/quart.py | 237 + .../sentry_sdk/integrations/ray.py | 141 + .../sentry_sdk/integrations/redis/__init__.py | 38 + .../integrations/redis/_async_common.py | 108 + .../integrations/redis/_sync_common.py | 113 + .../sentry_sdk/integrations/redis/consts.py | 19 + .../integrations/redis/modules/__init__.py | 0 .../integrations/redis/modules/caches.py | 121 + .../integrations/redis/modules/queries.py | 68 + .../sentry_sdk/integrations/redis/rb.py | 32 + .../sentry_sdk/integrations/redis/redis.py | 69 + .../integrations/redis/redis_cluster.py | 99 + .../redis/redis_py_cluster_legacy.py | 50 + .../sentry_sdk/integrations/redis/utils.py | 144 + aws/lambda_demo/sentry_sdk/integrations/rq.py | 161 + .../sentry_sdk/integrations/rust_tracing.py | 284 + .../sentry_sdk/integrations/sanic.py | 368 ++ .../sentry_sdk/integrations/serverless.py | 76 + .../sentry_sdk/integrations/socket.py | 92 + .../sentry_sdk/integrations/spark/__init__.py | 4 + .../integrations/spark/spark_driver.py | 285 + .../integrations/spark/spark_worker.py | 116 + .../sentry_sdk/integrations/sqlalchemy.py | 146 + .../sentry_sdk/integrations/starlette.py | 737 +++ .../sentry_sdk/integrations/starlite.py | 292 + .../sentry_sdk/integrations/stdlib.py | 265 + .../sentry_sdk/integrations/strawberry.py | 425 ++ .../sentry_sdk/integrations/sys_exit.py | 70 + .../sentry_sdk/integrations/threading.py | 121 + .../sentry_sdk/integrations/tornado.py | 223 + .../sentry_sdk/integrations/trytond.py | 50 + .../sentry_sdk/integrations/typer.py | 60 + .../sentry_sdk/integrations/unleash.py | 34 + .../sentry_sdk/integrations/wsgi.py | 310 ++ aws/lambda_demo/sentry_sdk/metrics.py | 970 ++++ aws/lambda_demo/sentry_sdk/monitor.py | 124 + .../sentry_sdk/profiler/__init__.py | 42 + .../profiler/continuous_profiler.py | 538 ++ .../profiler/transaction_profiler.py | 837 +++ aws/lambda_demo/sentry_sdk/profiler/utils.py | 199 + aws/lambda_demo/sentry_sdk/py.typed | 0 aws/lambda_demo/sentry_sdk/scope.py | 1744 ++++++ aws/lambda_demo/sentry_sdk/scrubber.py | 175 + aws/lambda_demo/sentry_sdk/serializer.py | 388 ++ aws/lambda_demo/sentry_sdk/session.py | 175 + aws/lambda_demo/sentry_sdk/sessions.py | 278 + aws/lambda_demo/sentry_sdk/spotlight.py | 232 + aws/lambda_demo/sentry_sdk/tracing.py | 1322 +++++ aws/lambda_demo/sentry_sdk/tracing_utils.py | 741 +++ aws/lambda_demo/sentry_sdk/transport.py | 910 +++ aws/lambda_demo/sentry_sdk/types.py | 24 + aws/lambda_demo/sentry_sdk/utils.py | 1958 +++++++ aws/lambda_demo/sentry_sdk/worker.py | 141 + .../urllib3-2.3.0.dist-info/INSTALLER | 1 + .../urllib3-2.3.0.dist-info/METADATA | 154 + .../urllib3-2.3.0.dist-info/RECORD | 79 + aws/lambda_demo/urllib3-2.3.0.dist-info/WHEEL | 4 + .../licenses/LICENSE.txt | 21 + aws/lambda_demo/urllib3/__init__.py | 211 + aws/lambda_demo/urllib3/_base_connection.py | 165 + aws/lambda_demo/urllib3/_collections.py | 479 ++ aws/lambda_demo/urllib3/_request_methods.py | 278 + aws/lambda_demo/urllib3/_version.py | 16 + aws/lambda_demo/urllib3/connection.py | 1044 ++++ aws/lambda_demo/urllib3/connectionpool.py | 1178 ++++ aws/lambda_demo/urllib3/contrib/__init__.py | 0 .../urllib3/contrib/emscripten/__init__.py | 16 + .../urllib3/contrib/emscripten/connection.py | 255 + .../emscripten/emscripten_fetch_worker.js | 110 + .../urllib3/contrib/emscripten/fetch.py | 708 +++ .../urllib3/contrib/emscripten/request.py | 22 + .../urllib3/contrib/emscripten/response.py | 285 + aws/lambda_demo/urllib3/contrib/pyopenssl.py | 554 ++ aws/lambda_demo/urllib3/contrib/socks.py | 228 + aws/lambda_demo/urllib3/exceptions.py | 327 ++ aws/lambda_demo/urllib3/fields.py | 341 ++ aws/lambda_demo/urllib3/filepost.py | 89 + aws/lambda_demo/urllib3/http2/__init__.py | 53 + aws/lambda_demo/urllib3/http2/connection.py | 356 ++ aws/lambda_demo/urllib3/http2/probe.py | 87 + aws/lambda_demo/urllib3/poolmanager.py | 637 +++ aws/lambda_demo/urllib3/py.typed | 2 + aws/lambda_demo/urllib3/response.py | 1278 +++++ aws/lambda_demo/urllib3/util/__init__.py | 42 + aws/lambda_demo/urllib3/util/connection.py | 137 + aws/lambda_demo/urllib3/util/proxy.py | 43 + aws/lambda_demo/urllib3/util/request.py | 258 + aws/lambda_demo/urllib3/util/response.py | 101 + aws/lambda_demo/urllib3/util/retry.py | 533 ++ aws/lambda_demo/urllib3/util/ssl_.py | 504 ++ .../urllib3/util/ssl_match_hostname.py | 159 + aws/lambda_demo/urllib3/util/ssltransport.py | 271 + aws/lambda_demo/urllib3/util/timeout.py | 275 + aws/lambda_demo/urllib3/util/url.py | 469 ++ aws/lambda_demo/urllib3/util/util.py | 42 + aws/lambda_demo/urllib3/util/wait.py | 124 + aws/requirements.txt | 2 + 210 files changed, 51349 insertions(+) create mode 100644 aws/inventory_update_sns.py create mode 100644 aws/lambda_demo/certifi-2024.12.14.dist-info/INSTALLER create mode 100644 aws/lambda_demo/certifi-2024.12.14.dist-info/LICENSE create mode 100644 aws/lambda_demo/certifi-2024.12.14.dist-info/METADATA create mode 100644 aws/lambda_demo/certifi-2024.12.14.dist-info/RECORD create mode 100644 aws/lambda_demo/certifi-2024.12.14.dist-info/WHEEL create mode 100644 aws/lambda_demo/certifi-2024.12.14.dist-info/top_level.txt create mode 100644 aws/lambda_demo/certifi/__init__.py create mode 100644 aws/lambda_demo/certifi/__main__.py create mode 100644 aws/lambda_demo/certifi/cacert.pem create mode 100644 aws/lambda_demo/certifi/core.py create mode 100644 aws/lambda_demo/certifi/py.typed create mode 100644 aws/lambda_demo/lambda_demo.zip create mode 100644 aws/lambda_demo/lambda_function.py create mode 100644 aws/lambda_demo/sentry_sdk-2.20.0.dist-info/INSTALLER create mode 100644 aws/lambda_demo/sentry_sdk-2.20.0.dist-info/LICENSE create mode 100644 aws/lambda_demo/sentry_sdk-2.20.0.dist-info/METADATA create mode 100644 aws/lambda_demo/sentry_sdk-2.20.0.dist-info/RECORD create mode 100644 aws/lambda_demo/sentry_sdk-2.20.0.dist-info/REQUESTED create mode 100644 aws/lambda_demo/sentry_sdk-2.20.0.dist-info/WHEEL create mode 100644 aws/lambda_demo/sentry_sdk-2.20.0.dist-info/entry_points.txt create mode 100644 aws/lambda_demo/sentry_sdk-2.20.0.dist-info/top_level.txt create mode 100644 aws/lambda_demo/sentry_sdk/__init__.py create mode 100644 aws/lambda_demo/sentry_sdk/_compat.py create mode 100644 aws/lambda_demo/sentry_sdk/_init_implementation.py create mode 100644 aws/lambda_demo/sentry_sdk/_lru_cache.py create mode 100644 aws/lambda_demo/sentry_sdk/_queue.py create mode 100644 aws/lambda_demo/sentry_sdk/_types.py create mode 100644 aws/lambda_demo/sentry_sdk/_werkzeug.py create mode 100644 aws/lambda_demo/sentry_sdk/ai/__init__.py create mode 100644 aws/lambda_demo/sentry_sdk/ai/monitoring.py create mode 100644 aws/lambda_demo/sentry_sdk/ai/utils.py create mode 100644 aws/lambda_demo/sentry_sdk/api.py create mode 100644 aws/lambda_demo/sentry_sdk/attachments.py create mode 100644 aws/lambda_demo/sentry_sdk/client.py create mode 100644 aws/lambda_demo/sentry_sdk/consts.py create mode 100644 aws/lambda_demo/sentry_sdk/crons/__init__.py create mode 100644 aws/lambda_demo/sentry_sdk/crons/api.py create mode 100644 aws/lambda_demo/sentry_sdk/crons/consts.py create mode 100644 aws/lambda_demo/sentry_sdk/crons/decorator.py create mode 100644 aws/lambda_demo/sentry_sdk/debug.py create mode 100644 aws/lambda_demo/sentry_sdk/envelope.py create mode 100644 aws/lambda_demo/sentry_sdk/feature_flags.py create mode 100644 aws/lambda_demo/sentry_sdk/hub.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/__init__.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/_asgi_common.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/_wsgi_common.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/aiohttp.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/anthropic.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/argv.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/ariadne.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/arq.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/asgi.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/asyncio.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/asyncpg.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/atexit.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/aws_lambda.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/beam.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/boto3.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/bottle.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/celery/__init__.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/celery/beat.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/celery/utils.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/chalice.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/clickhouse_driver.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/cloud_resource_context.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/cohere.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/dedupe.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/django/__init__.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/django/asgi.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/django/caching.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/django/middleware.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/django/signals_handlers.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/django/templates.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/django/transactions.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/django/views.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/dramatiq.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/excepthook.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/executing.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/falcon.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/fastapi.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/flask.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/gcp.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/gnu_backtrace.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/gql.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/graphene.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/grpc/__init__.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/grpc/aio/__init__.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/grpc/aio/client.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/grpc/aio/server.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/grpc/client.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/grpc/consts.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/grpc/server.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/httpx.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/huey.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/huggingface_hub.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/langchain.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/launchdarkly.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/litestar.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/logging.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/loguru.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/modules.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/openai.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/openfeature.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/opentelemetry/__init__.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/opentelemetry/consts.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/opentelemetry/integration.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/opentelemetry/propagator.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/opentelemetry/span_processor.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/pure_eval.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/pymongo.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/pyramid.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/quart.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/ray.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/redis/__init__.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/redis/_async_common.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/redis/_sync_common.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/redis/consts.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/redis/modules/__init__.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/redis/modules/caches.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/redis/modules/queries.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/redis/rb.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/redis/redis.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/redis/redis_cluster.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/redis/redis_py_cluster_legacy.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/redis/utils.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/rq.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/rust_tracing.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/sanic.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/serverless.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/socket.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/spark/__init__.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/spark/spark_driver.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/spark/spark_worker.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/sqlalchemy.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/starlette.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/starlite.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/stdlib.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/strawberry.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/sys_exit.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/threading.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/tornado.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/trytond.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/typer.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/unleash.py create mode 100644 aws/lambda_demo/sentry_sdk/integrations/wsgi.py create mode 100644 aws/lambda_demo/sentry_sdk/metrics.py create mode 100644 aws/lambda_demo/sentry_sdk/monitor.py create mode 100644 aws/lambda_demo/sentry_sdk/profiler/__init__.py create mode 100644 aws/lambda_demo/sentry_sdk/profiler/continuous_profiler.py create mode 100644 aws/lambda_demo/sentry_sdk/profiler/transaction_profiler.py create mode 100644 aws/lambda_demo/sentry_sdk/profiler/utils.py create mode 100644 aws/lambda_demo/sentry_sdk/py.typed create mode 100644 aws/lambda_demo/sentry_sdk/scope.py create mode 100644 aws/lambda_demo/sentry_sdk/scrubber.py create mode 100644 aws/lambda_demo/sentry_sdk/serializer.py create mode 100644 aws/lambda_demo/sentry_sdk/session.py create mode 100644 aws/lambda_demo/sentry_sdk/sessions.py create mode 100644 aws/lambda_demo/sentry_sdk/spotlight.py create mode 100644 aws/lambda_demo/sentry_sdk/tracing.py create mode 100644 aws/lambda_demo/sentry_sdk/tracing_utils.py create mode 100644 aws/lambda_demo/sentry_sdk/transport.py create mode 100644 aws/lambda_demo/sentry_sdk/types.py create mode 100644 aws/lambda_demo/sentry_sdk/utils.py create mode 100644 aws/lambda_demo/sentry_sdk/worker.py create mode 100644 aws/lambda_demo/urllib3-2.3.0.dist-info/INSTALLER create mode 100644 aws/lambda_demo/urllib3-2.3.0.dist-info/METADATA create mode 100644 aws/lambda_demo/urllib3-2.3.0.dist-info/RECORD create mode 100644 aws/lambda_demo/urllib3-2.3.0.dist-info/WHEEL create mode 100644 aws/lambda_demo/urllib3-2.3.0.dist-info/licenses/LICENSE.txt create mode 100644 aws/lambda_demo/urllib3/__init__.py create mode 100644 aws/lambda_demo/urllib3/_base_connection.py create mode 100644 aws/lambda_demo/urllib3/_collections.py create mode 100644 aws/lambda_demo/urllib3/_request_methods.py create mode 100644 aws/lambda_demo/urllib3/_version.py create mode 100644 aws/lambda_demo/urllib3/connection.py create mode 100644 aws/lambda_demo/urllib3/connectionpool.py create mode 100644 aws/lambda_demo/urllib3/contrib/__init__.py create mode 100644 aws/lambda_demo/urllib3/contrib/emscripten/__init__.py create mode 100644 aws/lambda_demo/urllib3/contrib/emscripten/connection.py create mode 100644 aws/lambda_demo/urllib3/contrib/emscripten/emscripten_fetch_worker.js create mode 100644 aws/lambda_demo/urllib3/contrib/emscripten/fetch.py create mode 100644 aws/lambda_demo/urllib3/contrib/emscripten/request.py create mode 100644 aws/lambda_demo/urllib3/contrib/emscripten/response.py create mode 100644 aws/lambda_demo/urllib3/contrib/pyopenssl.py create mode 100644 aws/lambda_demo/urllib3/contrib/socks.py create mode 100644 aws/lambda_demo/urllib3/exceptions.py create mode 100644 aws/lambda_demo/urllib3/fields.py create mode 100644 aws/lambda_demo/urllib3/filepost.py create mode 100644 aws/lambda_demo/urllib3/http2/__init__.py create mode 100644 aws/lambda_demo/urllib3/http2/connection.py create mode 100644 aws/lambda_demo/urllib3/http2/probe.py create mode 100644 aws/lambda_demo/urllib3/poolmanager.py create mode 100644 aws/lambda_demo/urllib3/py.typed create mode 100644 aws/lambda_demo/urllib3/response.py create mode 100644 aws/lambda_demo/urllib3/util/__init__.py create mode 100644 aws/lambda_demo/urllib3/util/connection.py create mode 100644 aws/lambda_demo/urllib3/util/proxy.py create mode 100644 aws/lambda_demo/urllib3/util/request.py create mode 100644 aws/lambda_demo/urllib3/util/response.py create mode 100644 aws/lambda_demo/urllib3/util/retry.py create mode 100644 aws/lambda_demo/urllib3/util/ssl_.py create mode 100644 aws/lambda_demo/urllib3/util/ssl_match_hostname.py create mode 100644 aws/lambda_demo/urllib3/util/ssltransport.py create mode 100644 aws/lambda_demo/urllib3/util/timeout.py create mode 100644 aws/lambda_demo/urllib3/util/url.py create mode 100644 aws/lambda_demo/urllib3/util/util.py create mode 100644 aws/lambda_demo/urllib3/util/wait.py create mode 100644 aws/requirements.txt diff --git a/.gitignore b/.gitignore index 39d0ceb6b..82737fd3a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,14 @@ express/node_modules react/build next/dist +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ # misc .idea diff --git a/aws/inventory_update_sns.py b/aws/inventory_update_sns.py new file mode 100644 index 000000000..887555dae --- /dev/null +++ b/aws/inventory_update_sns.py @@ -0,0 +1,411 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Purpose + +Shows how to use the AWS SDK for Python (Boto3) with Amazon Simple Notification +Service (Amazon SNS) to create notification topics, add subscribers, and publish +messages. +""" + +import json +import os +import logging +import random +import time +import dotenv +import sentry_sdk +from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration +import boto3 +from botocore.exceptions import ClientError + +dotenv.load_dotenv() + +sentry_sdk.init( + dsn=os.environ["FLASK_APP_DSN"], + # Add data like request headers and IP for users, if applicable; + # see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info + send_default_pii=True, + # Set traces_sample_rate to 1.0 to capture 100% + # of transactions for tracing. + traces_sample_rate=1.0, + # Set profiles_sample_rate to 1.0 to profile 100% + # of sampled transactions. + # We recommend adjusting this value in production. + profiles_sample_rate=1.0, + integrations=[ + AwsLambdaIntegration(), + ], +) + + +logger = logging.getLogger(__name__) + + +# snippet-start:[python.example_code.sns.SnsWrapper] +class SnsWrapper: + """Encapsulates Amazon SNS topic and subscription functions.""" + + def __init__(self, sns_resource): + """ + :param sns_resource: A Boto3 Amazon SNS resource. + """ + self.sns_resource = sns_resource + self.sns_client = boto3.client("sns") + + # snippet-end:[python.example_code.sns.SnsWrapper] + + # snippet-start:[python.example_code.sns.CreateTopic] + def create_topic(self, name): + """ + Creates a notification topic. + + :param name: The name of the topic to create. + :return: The newly created topic. + """ + try: + topic = self.sns_resource.create_topic(Name=name) + logger.info("Created topic %s with ARN %s.", name, topic.arn) + except ClientError: + logger.exception("Couldn't create topic %s.", name) + raise + else: + return topic + + # snippet-end:[python.example_code.sns.CreateTopic] + + def get_existing_topic_arn(self, topic_name): + topics = self.sns_client.list_topics()["Topics"] + print(topics) + for topic in topics: + if topic_name in topic["TopicArn"]: + return topic["TopicArn"] + return None + + # snippet-start:[python.example_code.sns.ListTopics] + def list_topics(self): + """ + Lists topics for the current account. + + :return: An iterator that yields the topics. + """ + try: + topics_iter = self.sns_resource.topics.all() + logger.info("Got topics.") + except ClientError: + logger.exception("Couldn't get topics.") + raise + else: + return topics_iter + + # snippet-end:[python.example_code.sns.ListTopics] + + # snippet-start:[python.example_code.sns.DeleteTopic] + @staticmethod + def delete_topic(topic): + """ + Deletes a topic. All subscriptions to the topic are also deleted. + """ + try: + topic.delete() + logger.info("Deleted topic %s.", topic.arn) + except ClientError: + logger.exception("Couldn't delete topic %s.", topic.arn) + raise + + # snippet-end:[python.example_code.sns.DeleteTopic] + + # snippet-start:[python.example_code.sns.Subscribe] + @staticmethod + def subscribe(topic, protocol, endpoint): + """ + Subscribes an endpoint to the topic. Some endpoint types, such as email, + must be confirmed before their subscriptions are active. When a subscription + is not confirmed, its Amazon Resource Number (ARN) is set to + 'PendingConfirmation'. + + :param topic: The topic to subscribe to. + :param protocol: The protocol of the endpoint, such as 'sms' or 'email'. + :param endpoint: The endpoint that receives messages, such as a phone number + (in E.164 format) for SMS messages, or an email address for + email messages. + :return: The newly added subscription. + """ + try: + subscription = topic.subscribe( + Protocol=protocol, Endpoint=endpoint, ReturnSubscriptionArn=True + ) + logger.info("Subscribed %s %s to topic %s.", protocol, endpoint, topic.arn) + except ClientError: + logger.exception( + "Couldn't subscribe %s %s to topic %s.", protocol, endpoint, topic.arn + ) + raise + else: + return subscription + + # snippet-end:[python.example_code.sns.Subscribe] + + # snippet-start:[python.example_code.sns.ListSubscriptions] + def list_subscriptions(self, topic=None): + """ + Lists subscriptions for the current account, optionally limited to a + specific topic. + + :param topic: When specified, only subscriptions to this topic are returned. + :return: An iterator that yields the subscriptions. + """ + try: + if topic is None: + subs_iter = self.sns_resource.subscriptions.all() + else: + subs_iter = topic.subscriptions.all() + logger.info("Got subscriptions.") + except ClientError: + logger.exception("Couldn't get subscriptions.") + raise + else: + return subs_iter + + # snippet-end:[python.example_code.sns.ListSubscriptions] + + # snippet-start:[python.example_code.sns.SetSubscriptionAttributes] + @staticmethod + def add_subscription_filter(subscription, attributes): + """ + Adds a filter policy to a subscription. A filter policy is a key and a + list of values that are allowed. When a message is published, it must have an + attribute that passes the filter or it will not be sent to the subscription. + + :param subscription: The subscription the filter policy is attached to. + :param attributes: A dictionary of key-value pairs that define the filter. + """ + try: + att_policy = {key: [value] for key, value in attributes.items()} + subscription.set_attributes( + AttributeName="FilterPolicy", AttributeValue=json.dumps(att_policy) + ) + logger.info("Added filter to subscription %s.", subscription.arn) + except ClientError: + logger.exception( + "Couldn't add filter to subscription %s.", subscription.arn + ) + raise + + # snippet-end:[python.example_code.sns.SetSubscriptionAttributes] + + # snippet-start:[python.example_code.sns.Unsubscribe] + @staticmethod + def delete_subscription(subscription): + """ + Unsubscribes and deletes a subscription. + """ + try: + subscription.delete() + logger.info("Deleted subscription %s.", subscription.arn) + except ClientError: + logger.exception("Couldn't delete subscription %s.", subscription.arn) + raise + + # snippet-end:[python.example_code.sns.Unsubscribe] + + # snippet-start:[python.example_code.sns.Publish_TextMessage] + def publish_text_message(self, phone_number, message): + """ + Publishes a text message directly to a phone number without need for a + subscription. + + :param phone_number: The phone number that receives the message. This must be + in E.164 format. For example, a United States phone + number might be +12065550101. + :param message: The message to send. + :return: The ID of the message. + """ + try: + response = self.sns_resource.meta.client.publish( + PhoneNumber=phone_number, Message=message + ) + message_id = response["MessageId"] + logger.info("Published message to %s.", phone_number) + except ClientError: + logger.exception("Couldn't publish message to %s.", phone_number) + raise + else: + return message_id + + # snippet-end:[python.example_code.sns.Publish_TextMessage] + + # snippet-start:[python.example_code.sns.Publish_MessageAttributes] + @staticmethod + def publish_message(topic, message, attributes): + """ + Publishes a message, with attributes, to a topic. Subscriptions can be filtered + based on message attributes so that a subscription receives messages only + when specified attributes are present. + + :param topic: The topic to publish to. + :param message: The message to publish. + :param attributes: The key-value attributes to attach to the message. Values + must be either `str` or `bytes`. + :return: The ID of the message. + """ + try: + att_dict = {} + for key, value in attributes.items(): + if isinstance(value, str): + att_dict[key] = {"DataType": "String", "StringValue": value} + elif isinstance(value, bytes): + att_dict[key] = {"DataType": "Binary", "BinaryValue": value} + response = topic.publish(Message=message, MessageAttributes=att_dict) + message_id = response["MessageId"] + logger.info( + "Published message with attributes %s to topic %s.", + attributes, + topic.arn, + ) + except ClientError: + logger.exception("Couldn't publish message to topic %s.", topic.arn) + raise + else: + return message_id + + # snippet-end:[python.example_code.sns.Publish_MessageAttributes] + + def publish_message(self, topic_arn, message: dict): + """ + Publishes a given dictionary to the SNS topic as a JSON string. + + :param message: Dictionary to publish. + :return: Response from the SNS publish API. + """ + # Convert the dictionary to a JSON string + json_message = json.dumps(message) + + # Publish the message + response = self.sns_client.publish( + TopicArn=topic_arn, + Message=json_message + ) + return response + + # snippet-start:[python.example_code.sns.Publish_MessageStructure] + @staticmethod + def publish_multi_message( + topic, subject, default_message, sms_message, email_message + ): + """ + Publishes a multi-format message to a topic. A multi-format message takes + different forms based on the protocol of the subscriber. For example, + an SMS subscriber might receive a short version of the message + while an email subscriber could receive a longer version. + + :param topic: The topic to publish to. + :param subject: The subject of the message. + :param default_message: The default version of the message. This version is + sent to subscribers that have protocols that are not + otherwise specified in the structured message. + :param sms_message: The version of the message sent to SMS subscribers. + :param email_message: The version of the message sent to email subscribers. + :return: The ID of the message. + """ + try: + message = { + "default": default_message, + "sms": sms_message, + "email": email_message, + } + response = topic.publish( + Message=json.dumps(message), Subject=subject, MessageStructure="json" + ) + message_id = response["MessageId"] + logger.info("Published multi-format message to topic %s.", topic.arn) + except ClientError: + logger.exception("Couldn't publish message to topic %s.", topic.arn) + raise + else: + return message_id + + +# snippet-end:[python.example_code.sns.Publish_MessageStructure] + +def generate_messages(num_messages=20): + """ + Generates a list of messages with the same plantNames and inventory, + but a random starting shipment_id that increments by 1 for each message. + + Ex. + message = { + 'plantNames' : ["fern", "spider", "cactus"], + 'inventory' : [10, 10 , 20], + 'shipment_id' : 1234 + } + """ + messages = [] + + # Generate a random starting shipment_id (e.g., between 1000 and 9999) + start_shipment_id = random.randint(1000, 9999) + + for i in range(num_messages): + message = { + 'plantNames': ["fern", "spider", "cactus"], + 'inventory': [10, 10, 20], + # Add i to keep shipment IDs sequential + 'shipment_id': start_shipment_id + i + } + messages.append(message) + + # Negative inv value for the recieve lambdas to throw an error to sentry + message = { + 'plantNames': ["fern", "spider", "cactus"], + 'inventory': [-10, 10, 20], + # Add i to keep shipment IDs sequential + 'shipment_id': 666 + } + messages.append(message) + + return messages + +def usage_demo(): + print("-" * 88) + print("Welcome to the Sentry Plant Inventory Update AWS SNS/SQS/Lambda Demo!") + print("-" * 88) + + logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") + + sns_wrapper = SnsWrapper(boto3.resource("sns")) + + messages = generate_messages(20) + + print(messages[0]) + + topic = sns_wrapper.get_existing_topic_arn('plant-inventory-updated') + + for msg in messages: + with sentry_sdk.start_transaction( + op="function", + name="inventory-updated-producer-transaction", + ): + # Create the span + with sentry_sdk.start_span( + op="queue.publish", + name="queue_producer", + ) as span: + # Set span data + span.set_data("messaging.message.id", msg.get('shipment_id')) + span.set_data("messaging.destination.name", "plant-inventory-updated") + span.set_data("messaging.message.body.size", len(msg)) + msg['headers'] = { + "sentry-trace": sentry_sdk.get_traceparent(), + "baggage": sentry_sdk.get_baggage(), + } + response = sns_wrapper.publish_message(topic, msg) + print("Publish response:", response["MessageId"]) + + + print("Thanks for the plants!") + print("-" * 88) + + +if __name__ == "__main__": + usage_demo() diff --git a/aws/lambda_demo/certifi-2024.12.14.dist-info/INSTALLER b/aws/lambda_demo/certifi-2024.12.14.dist-info/INSTALLER new file mode 100644 index 000000000..a1b589e38 --- /dev/null +++ b/aws/lambda_demo/certifi-2024.12.14.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/aws/lambda_demo/certifi-2024.12.14.dist-info/LICENSE b/aws/lambda_demo/certifi-2024.12.14.dist-info/LICENSE new file mode 100644 index 000000000..62b076cde --- /dev/null +++ b/aws/lambda_demo/certifi-2024.12.14.dist-info/LICENSE @@ -0,0 +1,20 @@ +This package contains a modified version of ca-bundle.crt: + +ca-bundle.crt -- Bundle of CA Root Certificates + +This is a bundle of X.509 certificates of public Certificate Authorities +(CA). These were automatically extracted from Mozilla's root certificates +file (certdata.txt). This file can be found in the mozilla source tree: +https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt +It contains the certificates in PEM format and therefore +can be directly used with curl / libcurl / php_curl, or with +an Apache+mod_ssl webserver for SSL client authentication. +Just configure this file as the SSLCACertificateFile.# + +***** BEGIN LICENSE BLOCK ***** +This Source Code Form is subject to the terms of the Mozilla Public License, +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain +one at http://mozilla.org/MPL/2.0/. + +***** END LICENSE BLOCK ***** +@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $ diff --git a/aws/lambda_demo/certifi-2024.12.14.dist-info/METADATA b/aws/lambda_demo/certifi-2024.12.14.dist-info/METADATA new file mode 100644 index 000000000..c4809863b --- /dev/null +++ b/aws/lambda_demo/certifi-2024.12.14.dist-info/METADATA @@ -0,0 +1,68 @@ +Metadata-Version: 2.1 +Name: certifi +Version: 2024.12.14 +Summary: Python package for providing Mozilla's CA Bundle. +Home-page: https://github.com/certifi/python-certifi +Author: Kenneth Reitz +Author-email: me@kennethreitz.com +License: MPL-2.0 +Project-URL: Source, https://github.com/certifi/python-certifi +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) +Classifier: Natural Language :: English +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Requires-Python: >=3.6 +License-File: LICENSE + +Certifi: Python SSL Certificates +================================ + +Certifi provides Mozilla's carefully curated collection of Root Certificates for +validating the trustworthiness of SSL certificates while verifying the identity +of TLS hosts. It has been extracted from the `Requests`_ project. + +Installation +------------ + +``certifi`` is available on PyPI. Simply install it with ``pip``:: + + $ pip install certifi + +Usage +----- + +To reference the installed certificate authority (CA) bundle, you can use the +built-in function:: + + >>> import certifi + + >>> certifi.where() + '/usr/local/lib/python3.7/site-packages/certifi/cacert.pem' + +Or from the command line:: + + $ python -m certifi + /usr/local/lib/python3.7/site-packages/certifi/cacert.pem + +Enjoy! + +.. _`Requests`: https://requests.readthedocs.io/en/master/ + +Addition/Removal of Certificates +-------------------------------- + +Certifi does not support any addition/removal or other modification of the +CA trust store content. This project is intended to provide a reliable and +highly portable root of trust to python deployments. Look to upstream projects +for methods to use alternate trust. diff --git a/aws/lambda_demo/certifi-2024.12.14.dist-info/RECORD b/aws/lambda_demo/certifi-2024.12.14.dist-info/RECORD new file mode 100644 index 000000000..719aac117 --- /dev/null +++ b/aws/lambda_demo/certifi-2024.12.14.dist-info/RECORD @@ -0,0 +1,14 @@ +certifi-2024.12.14.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +certifi-2024.12.14.dist-info/LICENSE,sha256=6TcW2mucDVpKHfYP5pWzcPBpVgPSH2-D8FPkLPwQyvc,989 +certifi-2024.12.14.dist-info/METADATA,sha256=z71eRGTFszr4qsHenZ_vG2Fd5bV9PBWmJgShthc8IkY,2274 +certifi-2024.12.14.dist-info/RECORD,, +certifi-2024.12.14.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91 +certifi-2024.12.14.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8 +certifi/__init__.py,sha256=LqjNcwt1sYSS3uhPXrf6jJzVCuHtNVpuirg5rb7mVm8,94 +certifi/__main__.py,sha256=xBBoj905TUWBLRGANOcf7oi6e-3dMP4cEoG9OyMs11g,243 +certifi/__pycache__/__init__.cpython-311.pyc,, +certifi/__pycache__/__main__.cpython-311.pyc,, +certifi/__pycache__/core.cpython-311.pyc,, +certifi/cacert.pem,sha256=gHiXJU84Oif0XkT0llbzeKurIUHt5DpK08JCCll90j8,294769 +certifi/core.py,sha256=qRDDFyXVJwTB_EmoGppaXU_R9qCZvhl-EzxPMuV3nTA,4426 +certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/aws/lambda_demo/certifi-2024.12.14.dist-info/WHEEL b/aws/lambda_demo/certifi-2024.12.14.dist-info/WHEEL new file mode 100644 index 000000000..ae527e7d6 --- /dev/null +++ b/aws/lambda_demo/certifi-2024.12.14.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (75.6.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/aws/lambda_demo/certifi-2024.12.14.dist-info/top_level.txt b/aws/lambda_demo/certifi-2024.12.14.dist-info/top_level.txt new file mode 100644 index 000000000..963eac530 --- /dev/null +++ b/aws/lambda_demo/certifi-2024.12.14.dist-info/top_level.txt @@ -0,0 +1 @@ +certifi diff --git a/aws/lambda_demo/certifi/__init__.py b/aws/lambda_demo/certifi/__init__.py new file mode 100644 index 000000000..ee8686bec --- /dev/null +++ b/aws/lambda_demo/certifi/__init__.py @@ -0,0 +1,4 @@ +from .core import contents, where + +__all__ = ["contents", "where"] +__version__ = "2024.12.14" diff --git a/aws/lambda_demo/certifi/__main__.py b/aws/lambda_demo/certifi/__main__.py new file mode 100644 index 000000000..8945b5da8 --- /dev/null +++ b/aws/lambda_demo/certifi/__main__.py @@ -0,0 +1,12 @@ +import argparse + +from certifi import contents, where + +parser = argparse.ArgumentParser() +parser.add_argument("-c", "--contents", action="store_true") +args = parser.parse_args() + +if args.contents: + print(contents()) +else: + print(where()) diff --git a/aws/lambda_demo/certifi/cacert.pem b/aws/lambda_demo/certifi/cacert.pem new file mode 100644 index 000000000..f2d66010a --- /dev/null +++ b/aws/lambda_demo/certifi/cacert.pem @@ -0,0 +1,4856 @@ + +# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Label: "GlobalSign Root CA" +# Serial: 4835703278459707669005204 +# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a +# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c +# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME +HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Label: "Entrust.net Premium 2048 Secure Server CA" +# Serial: 946069240 +# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 +# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 +# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 +MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub +j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo +U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf +zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b +u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ +bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er +fF6adulZkMV8gzURZVE= +-----END CERTIFICATE----- + +# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Label: "Baltimore CyberTrust Root" +# Serial: 33554617 +# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 +# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 +# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. +# Label: "Entrust Root Certification Authority" +# Serial: 1164660820 +# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 +# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 +# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c +-----BEGIN CERTIFICATE----- +MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 +Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW +KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl +cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw +NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw +NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy +ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV +BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo +Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 +4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 +KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI +rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi +94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB +sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi +gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo +kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE +vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA +A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t +O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua +AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP +9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ +eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m +0vdXcDazv/wor3ElhVsT/h5/WrQ8 +-----END CERTIFICATE----- + +# Issuer: CN=AAA Certificate Services O=Comodo CA Limited +# Subject: CN=AAA Certificate Services O=Comodo CA Limited +# Label: "Comodo AAA Services root" +# Serial: 1 +# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 +# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 +# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2" +# Serial: 1289 +# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b +# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 +# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 +-----BEGIN CERTIFICATE----- +MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa +GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg +Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J +WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB +rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp ++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 +ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i +Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz +PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og +/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH +oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI +yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud +EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 +A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL +MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT +ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f +BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn +g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl +fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K +WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha +B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc +hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR +TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD +mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z +ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y +4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza +8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3" +# Serial: 1478 +# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf +# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 +# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 +-----BEGIN CERTIFICATE----- +MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x +GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv +b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV +BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W +YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM +V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB +4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr +H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd +8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv +vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT +mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe +btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc +T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt +WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ +c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A +4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD +VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG +CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 +aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 +aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu +dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw +czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G +A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg +Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 +7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem +d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd ++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B +4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN +t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x +DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 +k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s +zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j +Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT +mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK +4SVhM7JZG+Ju1zdXtg2pEto= +-----END CERTIFICATE----- + +# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Label: "XRamp Global CA Root" +# Serial: 107108908803651509692980124233745014957 +# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 +# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 +# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB +gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk +MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY +UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx +NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 +dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy +dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 +38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP +KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q +DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 +qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa +JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi +PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P +BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs +jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 +eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR +vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa +IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy +i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ +O+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Label: "Go Daddy Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 +# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 +# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- + +# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Label: "Starfield Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 +# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a +# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root CA" +# Serial: 17154717934120587862167794914071425081 +# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 +# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 +# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c +-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c +JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP +mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ +wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 +VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ +AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB +AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun +pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC +dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf +fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm +NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx +H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe ++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root CA" +# Serial: 10944719598952040374951832963794454346 +# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e +# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 +# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB +CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 +nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt +43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P +T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 +gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR +TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw +DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr +hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg +06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF +PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls +YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk +CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert High Assurance EV Root CA" +# Serial: 3553400076410547919724730734378100087 +# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a +# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 +# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG +# Label: "SwissSign Gold CA - G2" +# Serial: 13492815561806991280 +# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 +# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 +# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG +# Label: "SwissSign Silver CA - G2" +# Serial: 5700383053117599563 +# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 +# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb +# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=SecureTrust CA O=SecureTrust Corporation +# Subject: CN=SecureTrust CA O=SecureTrust Corporation +# Label: "SecureTrust CA" +# Serial: 17199774589125277788362757014266862032 +# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 +# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 +# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 +-----BEGIN CERTIFICATE----- +MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz +MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv +cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz +Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO +0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao +wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj +7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS +8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT +BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg +JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 +6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ +3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm +D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS +CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR +3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= +-----END CERTIFICATE----- + +# Issuer: CN=Secure Global CA O=SecureTrust Corporation +# Subject: CN=Secure Global CA O=SecureTrust Corporation +# Label: "Secure Global CA" +# Serial: 9751836167731051554232119481456978597 +# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de +# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b +# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 +-----BEGIN CERTIFICATE----- +MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK +MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x +GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx +MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg +Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ +iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa +/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ +jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI +HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 +sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w +gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw +KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG +AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L +URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO +H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm +I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY +iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc +f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW +-----END CERTIFICATE----- + +# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO Certification Authority O=COMODO CA Limited +# Label: "COMODO Certification Authority" +# Serial: 104350513648249232941998508985834464573 +# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 +# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b +# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 +-----BEGIN CERTIFICATE----- +MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB +gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV +BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw +MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl +YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P +RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 +UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI +2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 +Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp ++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ +DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O +nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW +/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g +PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u +QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY +SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv +IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ +RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 +zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd +BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB +ZQ== +-----END CERTIFICATE----- + +# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited +# Label: "COMODO ECC Certification Authority" +# Serial: 41578283867086692638256921589707938090 +# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 +# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 +# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 +-----BEGIN CERTIFICATE----- +MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT +IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw +MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy +ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N +T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv +biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR +FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J +cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW +BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm +fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv +GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= +-----END CERTIFICATE----- + +# Issuer: CN=Certigna O=Dhimyotis +# Subject: CN=Certigna O=Dhimyotis +# Label: "Certigna" +# Serial: 18364802974209362175 +# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff +# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 +# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d +-----BEGIN CERTIFICATE----- +MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV +BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X +DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ +BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 +QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny +gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw +zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q +130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 +JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw +ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT +AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj +AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG +9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h +bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc +fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu +HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w +t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw +WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== +-----END CERTIFICATE----- + +# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority +# Label: "ePKI Root Certification Authority" +# Serial: 28956088682735189655030529057352760477 +# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 +# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 +# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 +-----BEGIN CERTIFICATE----- +MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw +IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL +SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH +SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh +ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X +DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 +TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ +fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA +sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU +WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS +nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH +dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip +NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC +AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF +MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH +ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB +uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl +PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP +JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ +gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 +j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 +5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB +o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS +/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z +Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE +W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D +hNQ+IIX3Sj0rnP0qCglN6oH4EZw= +-----END CERTIFICATE----- + +# Issuer: O=certSIGN OU=certSIGN ROOT CA +# Subject: O=certSIGN OU=certSIGN ROOT CA +# Label: "certSIGN ROOT CA" +# Serial: 35210227249154 +# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 +# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b +# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT +AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD +QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP +MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do +0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ +UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d +RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ +OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv +JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C +AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O +BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ +LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY +MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ +44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I +Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw +i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN +9u6wWk5JRFRYX0KD +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) +# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" +# Serial: 80544274841616 +# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 +# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 +# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG +EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 +MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl +cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR +dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB +pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM +b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm +aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz +IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT +lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz +AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 +VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG +ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 +BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG +AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M +U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh +bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C ++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC +bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F +uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 +XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= +-----END CERTIFICATE----- + +# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. +# Label: "Microsec e-Szigno Root CA 2009" +# Serial: 14014712776195784473 +# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 +# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e +# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G +CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y +OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx +FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp +Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o +dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP +kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc +cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U +fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 +N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC +xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 ++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM +Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG +SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h +mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk +ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 +tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c +2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t +HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 +# Label: "GlobalSign Root CA - R3" +# Serial: 4835703278459759426209954 +# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 +# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad +# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f +-----END CERTIFICATE----- + +# Issuer: CN=Izenpe.com O=IZENPE S.A. +# Subject: CN=Izenpe.com O=IZENPE S.A. +# Label: "Izenpe.com" +# Serial: 917563065490389241595536686991402621 +# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 +# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 +# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. +# Label: "Go Daddy Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 +# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b +# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT +EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp +ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz +NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH +EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE +AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD +E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH +/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy +DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh +GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR +tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA +AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX +WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu +9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr +gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo +2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO +LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI +4uJEvlz36hz1 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 +# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e +# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs +ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw +MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 +b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj +aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp +Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg +nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 +HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N +Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN +dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 +HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G +CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU +sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 +4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg +8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K +pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 +mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 +-----END CERTIFICATE----- + +# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. +# Label: "Starfield Services Root Certificate Authority - G2" +# Serial: 0 +# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 +# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f +# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx +EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT +HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs +ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 +MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD +VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy +ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy +dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p +OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 +8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K +Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe +hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk +6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q +AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI +bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB +ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z +qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd +iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn +0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN +sSi6 +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Commercial O=AffirmTrust +# Subject: CN=AffirmTrust Commercial O=AffirmTrust +# Label: "AffirmTrust Commercial" +# Serial: 8608355977964138876 +# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 +# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 +# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP +Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr +ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL +MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 +yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr +VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ +nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG +XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj +vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt +Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g +N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC +nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Networking O=AffirmTrust +# Subject: CN=AffirmTrust Networking O=AffirmTrust +# Label: "AffirmTrust Networking" +# Serial: 8957382827206547757 +# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f +# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f +# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b +-----BEGIN CERTIFICATE----- +MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz +dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL +MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp +cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y +YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua +kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL +QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp +6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG +yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i +QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ +KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO +tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu +QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ +Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u +olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 +x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium O=AffirmTrust +# Subject: CN=AffirmTrust Premium O=AffirmTrust +# Label: "AffirmTrust Premium" +# Serial: 7893706540734352110 +# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 +# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 +# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE +BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz +dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG +A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U +cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf +qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ +JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ ++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS +s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 +HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 +70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG +V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S +qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S +5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia +C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX +OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE +FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 +KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg +Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B +8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ +MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc +0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ +u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF +u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH +YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 +GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO +RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e +KeC2uAloGRwYQw== +-----END CERTIFICATE----- + +# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust +# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust +# Label: "AffirmTrust Premium ECC" +# Serial: 8401224907861490260 +# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d +# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb +# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 +-----BEGIN CERTIFICATE----- +MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC +VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ +cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ +BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt +VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D +0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 +ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G +A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs +aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I +flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA" +# Serial: 279744 +# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 +# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e +# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e +-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM +MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D +ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU +cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 +WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg +Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw +IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH +UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM +TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU +BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM +kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x +AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV +HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y +sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL +I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 +J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY +VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI +03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA +# Label: "TWCA Root Certification Authority" +# Serial: 1 +# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 +# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 +# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES +MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU +V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz +WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO +LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE +AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH +K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX +RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z +rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx +3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq +hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC +MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls +XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D +lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn +aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ +YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== +-----END CERTIFICATE----- + +# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 +# Label: "Security Communication RootCA2" +# Serial: 0 +# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 +# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 +# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl +MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe +U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX +DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy +dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj +YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV +OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr +zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM +VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ +hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO +ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw +awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs +OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF +coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc +okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 +t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy +1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ +SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 +-----END CERTIFICATE----- + +# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 +# Label: "Actalis Authentication Root CA" +# Serial: 6271844772424770508 +# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 +# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac +# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 +-----BEGIN CERTIFICATE----- +MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE +BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w +MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 +IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC +SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 +ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv +UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX +4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 +KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ +gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb +rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ +51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F +be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe +KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F +v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn +fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 +jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz +ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt +ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL +e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 +jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz +WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V +SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j +pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX +X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok +fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R +K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU +ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU +LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT +LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 2 Root CA" +# Serial: 2 +# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 +# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 +# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr +6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV +L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 +1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx +MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ +QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB +arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr +Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi +FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS +P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN +9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz +uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h +9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s +A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t +OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo ++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 +KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 +DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us +H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ +I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 +5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h +3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz +Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= +-----END CERTIFICATE----- + +# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 +# Label: "Buypass Class 3 Root CA" +# Serial: 2 +# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec +# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 +# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d +-----BEGIN CERTIFICATE----- +MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd +MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg +Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow +TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw +HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y +ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E +N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 +tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX +0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c +/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X +KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY +zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS +O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D +34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP +K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 +AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv +Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj +QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV +cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS +IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 +HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa +O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv +033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u +dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE +kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 +3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD +u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq +4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 3" +# Serial: 1 +# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef +# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 +# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN +8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ +RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 +hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 +ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM +EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 +A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy +WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ +1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 +6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT +91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml +e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p +TpPDpFQUWw== +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 2009" +# Serial: 623603 +# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f +# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 +# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 +-----BEGIN CERTIFICATE----- +MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha +ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM +HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 +UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 +tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R +ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM +lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp +/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G +A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj +dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy +MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl +cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js +L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL +BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni +acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 +o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K +zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 +PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y +Johw1+qRzT65ysCQblrGXnRl11z+o+I= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH +# Label: "D-TRUST Root Class 3 CA 2 EV 2009" +# Serial: 623604 +# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 +# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 +# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF +MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD +bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw +NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV +BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn +ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 +3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z +qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR +p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 +HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw +ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea +HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw +Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh +c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E +RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt +dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku +Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp +3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 +nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF +CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na +xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX +KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 +-----END CERTIFICATE----- + +# Issuer: CN=CA Disig Root R2 O=Disig a.s. +# Subject: CN=CA Disig Root R2 O=Disig a.s. +# Label: "CA Disig Root R2" +# Serial: 10572350602393338211 +# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 +# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 +# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 +-----BEGIN CERTIFICATE----- +MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV +BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu +MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy +MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx +EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw +ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe +NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH +PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I +x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe +QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR +yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO +QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 +H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ +QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD +i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs +nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 +rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud +DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI +hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM +tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf +GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb +lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka ++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal +TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i +nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 +gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr +G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os +zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x +L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL +-----END CERTIFICATE----- + +# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV +# Label: "ACCVRAIZ1" +# Serial: 6828503384748696800 +# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 +# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 +# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 +-----BEGIN CERTIFICATE----- +MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE +AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw +CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ +BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND +VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb +qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY +HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo +G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA +lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr +IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ +0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH +k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 +4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO +m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa +cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl +uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI +KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls +ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG +AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 +VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT +VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG +CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA +cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA +QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA +7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA +cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA +QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA +czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu +aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt +aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud +DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF +BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp +D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU +JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m +AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD +vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms +tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH +7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h +I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA +h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF +d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H +pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 +-----END CERTIFICATE----- + +# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA Global Root CA" +# Serial: 3262 +# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 +# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 +# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx +EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT +VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 +NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT +B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF +10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz +0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh +MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH +zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc +46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 +yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi +laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP +oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA +BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE +qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm +4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL +1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn +LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF +H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo +RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ +nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh +15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW +6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW +nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j +wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz +aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy +KwbQBM0= +-----END CERTIFICATE----- + +# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Label: "TeliaSonera Root CA v1" +# Serial: 199041966741090107964904287217786801558 +# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c +# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 +# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 2" +# Serial: 1 +# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a +# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 +# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot 2011 O=Atos +# Subject: CN=Atos TrustedRoot 2011 O=Atos +# Label: "Atos TrustedRoot 2011" +# Serial: 6643877497813316402 +# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 +# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 +# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Label: "GlobalSign ECC Root CA - R5" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Label: "IdenTrust Commercial Root CA 1" +# Serial: 13298821034946342390520003877796839426 +# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 +# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 +# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Label: "IdenTrust Public Sector Root CA 1" +# Serial: 13298821034946342390521976156843933698 +# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba +# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd +# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G2" +# Serial: 1246989352 +# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 +# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 +# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - EC1" +# Serial: 51543124481930649114116133369 +# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc +# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 +# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority +# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority +# Label: "CFCA EV ROOT" +# Serial: 407555286 +# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 +# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 +# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GB CA" +# Serial: 157768595616588414422159278966750757568 +# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d +# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed +# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- + +# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. +# Label: "SZAFIR ROOT CA2" +# Serial: 357043034767186914217277344587386743377558296292 +# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 +# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de +# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 +ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw +NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L +cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg +Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN +QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT +3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw +3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 +3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 +BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN +XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF +AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw +8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG +nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP +oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy +d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg +LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Network CA 2" +# Serial: 44979900017204383099463764357512596969 +# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 +# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 +# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 +-----BEGIN CERTIFICATE----- +MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB +gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu +QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG +A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz +OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ +VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 +b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA +DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn +0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB +OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE +fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E +Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m +o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i +sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW +OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez +Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS +adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n +3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC +AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ +F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf +CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 +XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm +djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ +WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb +AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq +P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko +b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj +XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P +5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi +DrW5viSP +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce +# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 +# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 +-----BEGIN CERTIFICATE----- +MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix +DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k +IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT +N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v +dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG +A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh +ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx +QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA +4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 +AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 +4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C +ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV +9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD +gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 +Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq +NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko +LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc +Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV +HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd +ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I +XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI +M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot +9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V +Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea +j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh +X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ +l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf +bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 +pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK +e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 +vm9qp/UsQu0yrbYhnr68 +-----END CERTIFICATE----- + +# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" +# Serial: 0 +# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef +# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 +# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 +-----BEGIN CERTIFICATE----- +MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN +BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl +bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv +b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ +BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj +YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 +MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 +dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg +QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa +jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC +MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi +C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep +lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof +TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X1 O=Internet Security Research Group +# Subject: CN=ISRG Root X1 O=Internet Security Research Group +# Label: "ISRG Root X1" +# Serial: 172886928669790476064670243504169061120 +# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e +# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 +# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- + +# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM +# Label: "AC RAIZ FNMT-RCM" +# Serial: 485876308206448804701554682760554759 +# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d +# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20 +# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx +CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ +WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ +BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG +Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ +yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf +BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz +WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF +tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z +374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC +IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL +mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 +wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS +MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 +ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet +UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H +YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 +LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD +nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 +RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM +LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf +77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N +JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm +fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp +6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp +1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B +9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok +RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv +uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 1 O=Amazon +# Subject: CN=Amazon Root CA 1 O=Amazon +# Label: "Amazon Root CA 1" +# Serial: 143266978916655856878034712317230054538369994 +# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6 +# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16 +# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 2 O=Amazon +# Subject: CN=Amazon Root CA 2 O=Amazon +# Label: "Amazon Root CA 2" +# Serial: 143266982885963551818349160658925006970653239 +# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66 +# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a +# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4 +-----BEGIN CERTIFICATE----- +MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK +gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ +W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg +1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K +8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r +2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me +z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR +8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj +mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz +7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 ++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI +0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm +UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 +LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY ++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS +k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl +7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm +btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl +urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ +fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 +n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE +76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H +9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT +4PsJYGw= +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 3 O=Amazon +# Subject: CN=Amazon Root CA 3 O=Amazon +# Label: "Amazon Root CA 3" +# Serial: 143266986699090766294700635381230934788665930 +# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87 +# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e +# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4 +-----BEGIN CERTIFICATE----- +MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl +ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr +ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr +BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM +YyRIHN8wfdVoOw== +-----END CERTIFICATE----- + +# Issuer: CN=Amazon Root CA 4 O=Amazon +# Subject: CN=Amazon Root CA 4 O=Amazon +# Label: "Amazon Root CA 4" +# Serial: 143266989758080763974105200630763877849284878 +# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd +# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be +# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92 +-----BEGIN CERTIFICATE----- +MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 +MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g +Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG +A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg +Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi +9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk +M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB +MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw +CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW +1KyLa2tJElMzrdfkviT8tQp21KW8EA== +-----END CERTIFICATE----- + +# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM +# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" +# Serial: 1 +# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49 +# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca +# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16 +-----BEGIN CERTIFICATE----- +MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx +GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp +bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w +KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 +BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy +dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG +EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll +IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU +QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT +TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg +LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 +a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr +LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr +N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X +YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ +iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f +AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH +V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh +AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf +IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 +lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c +8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf +lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= +-----END CERTIFICATE----- + +# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. +# Label: "GDCA TrustAUTH R5 ROOT" +# Serial: 9009899650740120186 +# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4 +# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4 +# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93 +-----BEGIN CERTIFICATE----- +MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE +BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ +IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 +MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV +BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w +HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj +Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj +TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u +KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj +qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm +MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 +ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP +zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk +L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC +jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA +HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC +AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg +p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm +DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 +COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry +L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf +JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg +IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io +2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV +09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ +XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq +T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe +MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation +# Label: "SSL.com Root Certification Authority RSA" +# Serial: 8875640296558310041 +# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29 +# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb +# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69 +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE +BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK +DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz +OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv +bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R +xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX +qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC +C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 +6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh +/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF +YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E +JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc +US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 +ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm ++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi +M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G +A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV +cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc +Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs +PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ +q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 +cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr +a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I +H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y +K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu +nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf +oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY +Ic2wBlX7Jz9TkHCpBB5XJ7k= +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com Root Certification Authority ECC" +# Serial: 8495723813297216424 +# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e +# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a +# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65 +-----BEGIN CERTIFICATE----- +MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz +WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 +b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS +b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI +7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg +CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud +EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD +VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T +kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ +gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority RSA R2" +# Serial: 6248227494352943350 +# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95 +# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a +# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c +-----BEGIN CERTIFICATE----- +MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV +BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE +CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy +MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G +A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD +DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq +M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf +OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa +4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 +HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR +aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA +b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ +Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV +PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO +pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu +UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY +MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV +HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 +9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW +s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 +Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg +cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM +79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz +/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt +ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm +Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK +QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ +w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi +S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 +mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation +# Label: "SSL.com EV Root Certification Authority ECC" +# Serial: 3182246526754555285 +# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90 +# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d +# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8 +-----BEGIN CERTIFICATE----- +MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC +VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T +U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx +NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv +dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv +bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA +VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku +WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX +5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ +ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg +h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 +# Label: "GlobalSign Root CA - R6" +# Serial: 1417766617973444989252670301619537 +# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae +# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1 +# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69 +-----BEGIN CERTIFICATE----- +MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg +MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx +MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET +MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI +xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k +ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD +aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw +LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw +1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX +k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 +SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h +bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n +WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY +rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce +MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu +bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN +nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt +Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 +55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj +vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf +cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz +oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp +nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs +pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v +JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R +8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 +5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= +-----END CERTIFICATE----- + +# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GC CA" +# Serial: 44084345621038548146064804565436152554 +# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23 +# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31 +# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- + +# Issuer: CN=UCA Global G2 Root O=UniTrust +# Subject: CN=UCA Global G2 Root O=UniTrust +# Label: "UCA Global G2 Root" +# Serial: 124779693093741543919145257850076631279 +# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8 +# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a +# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c +-----BEGIN CERTIFICATE----- +MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9 +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH +bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x +CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds +b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr +b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9 +kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm +VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R +VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc +C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj +tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY +D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv +j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl +NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6 +iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP +O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV +ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj +L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 +1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl +1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU +b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV +PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj +y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb +EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg +DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI ++Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy +YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX +UB+K+wb1whnw0A== +-----END CERTIFICATE----- + +# Issuer: CN=UCA Extended Validation Root O=UniTrust +# Subject: CN=UCA Extended Validation Root O=UniTrust +# Label: "UCA Extended Validation Root" +# Serial: 106100277556486529736699587978573607008 +# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2 +# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a +# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF +eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx +MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV +BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog +D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS +sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop +O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk +sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi +c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj +VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz +KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/ +TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G +sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs +1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD +fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T +AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN +l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR +ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ +VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5 +c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp +4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s +t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj +2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO +vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C +xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx +cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM +fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax +-----END CERTIFICATE----- + +# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 +# Label: "Certigna Root CA" +# Serial: 269714418870597844693661054334862075617 +# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77 +# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43 +# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68 +-----BEGIN CERTIFICATE----- +MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw +WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw +MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x +MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD +VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX +BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw +ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO +ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M +CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu +I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm +TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh +C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf +ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz +IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT +Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k +JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 +hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB +GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE +FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of +1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov +L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo +dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr +aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq +hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L +6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG +HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 +0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB +lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi +o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 +gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v +faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 +Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh +jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw +3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign Root CA - G1" +# Serial: 235931866688319308814040 +# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac +# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c +# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67 +-----BEGIN CERTIFICATE----- +MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD +VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU +ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH +MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO +MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv +Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz +f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO +8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq +d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM +tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt +Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB +o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD +AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x +PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM +wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d +GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH +6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby +RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx +iN66zB+Afko= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI +# Label: "emSign ECC Root CA - G3" +# Serial: 287880440101571086945156 +# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40 +# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1 +# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b +-----BEGIN CERTIFICATE----- +MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG +EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo +bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g +RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ +TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s +b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 +WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS +fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB +zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB +CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD ++JbNR6iC8hZVdyR+EhCVBCyj +-----END CERTIFICATE----- + +# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI +# Label: "emSign Root CA - C1" +# Serial: 825510296613316004955058 +# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68 +# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01 +# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG +A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg +SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v +dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ +BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ +HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH +3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH +GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c +xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 +aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq +TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 +/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 +kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG +YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT ++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo +WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= +-----END CERTIFICATE----- + +# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI +# Label: "emSign ECC Root CA - C3" +# Serial: 582948710642506000014504 +# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5 +# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66 +# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3 +-----BEGIN CERTIFICATE----- +MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG +EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx +IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw +MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln +biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND +IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci +MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti +sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O +BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c +3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J +0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== +-----END CERTIFICATE----- + +# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post +# Label: "Hongkong Post Root CA 3" +# Serial: 46170865288971385588281144162979347873371282084 +# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0 +# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02 +# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6 +-----BEGIN CERTIFICATE----- +MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL +BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ +SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n +a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5 +NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT +CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u +Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO +dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI +VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV +9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY +2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY +vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt +bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb +x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+ +l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK +TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj +Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e +i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw +DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG +7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk +MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr +gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk +GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS +3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm +Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+ +l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c +JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP +L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa +LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG +mpv0 +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft ECC Root Certificate Authority 2017" +# Serial: 136839042543790627607696632466672567020 +# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67 +# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5 +# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02 +-----BEGIN CERTIFICATE----- +MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD +VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw +MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV +UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy +b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR +ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb +hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 +FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV +L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB +iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= +-----END CERTIFICATE----- + +# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation +# Label: "Microsoft RSA Root Certificate Authority 2017" +# Serial: 40975477897264996090493496164228220339 +# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47 +# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74 +# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0 +-----BEGIN CERTIFICATE----- +MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl +MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw +NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG +EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N +aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ +Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 +ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 +HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm +gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ +jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc +aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG +YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 +W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K +UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH ++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q +W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC +NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC +LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC +gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 +tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh +SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 +TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 +pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR +xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp +GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 +dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN +AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB +RA+GsCyRxj3qrg+E +-----END CERTIFICATE----- + +# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd. +# Label: "e-Szigno Root CA 2017" +# Serial: 411379200276854331539784714 +# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98 +# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1 +# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99 +-----BEGIN CERTIFICATE----- +MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV +BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk +LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv +b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ +BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg +THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v +IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv +xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H +Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G +A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB +eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo +jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ ++efcMQ== +-----END CERTIFICATE----- + +# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2 +# Label: "certSIGN Root CA G2" +# Serial: 313609486401300475190 +# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7 +# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32 +# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05 +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV +BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g +Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ +BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ +R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF +dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw +vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ +uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp +n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs +cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW +xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P +rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF +DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx +DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy +LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C +eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ +d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq +kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC +b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl +qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 +OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c +NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk +ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO +pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj +03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk +PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE +1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX +QRBdJ3NghVdJIgc= +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global Certification Authority" +# Serial: 1846098327275375458322922162 +# MD5 Fingerprint: f8:1c:18:2d:2f:ba:5f:6d:a1:6c:bc:c7:ab:91:c7:0e +# SHA1 Fingerprint: 2f:8f:36:4f:e1:58:97:44:21:59:87:a5:2a:9a:d0:69:95:26:7f:b5 +# SHA256 Fingerprint: 97:55:20:15:f5:dd:fc:3c:87:88:c0:06:94:45:55:40:88:94:45:00:84:f1:00:86:70:86:bc:1a:2b:b5:8d:c8 +-----BEGIN CERTIFICATE----- +MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw +CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x +ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 +c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx +OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI +SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI +b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn +swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu +7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8 +1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW +80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP +JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l +RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw +hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10 +coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc +BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n +twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud +EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud +DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W +0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe +uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q +lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB +aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE +sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT +MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe +qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh +VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 +h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 +EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK +yeC2nOnOcXHebD8WpHk= +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global ECC P256 Certification Authority" +# Serial: 4151900041497450638097112925 +# MD5 Fingerprint: 5b:44:e3:8d:5d:36:86:26:e8:0d:05:d2:59:a7:83:54 +# SHA1 Fingerprint: b4:90:82:dd:45:0c:be:8b:5b:b1:66:d3:e2:a4:08:26:cd:ed:42:cf +# SHA256 Fingerprint: 94:5b:bc:82:5e:a5:54:f4:89:d1:fd:51:a7:3d:df:2e:a6:24:ac:70:19:a0:52:05:22:5c:22:a7:8c:cf:a8:b4 +-----BEGIN CERTIFICATE----- +MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG +SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN +FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w +DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw +CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh +DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 +-----END CERTIFICATE----- + +# Issuer: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. +# Subject: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. +# Label: "Trustwave Global ECC P384 Certification Authority" +# Serial: 2704997926503831671788816187 +# MD5 Fingerprint: ea:cf:60:c4:3b:b9:15:29:40:a1:97:ed:78:27:93:d6 +# SHA1 Fingerprint: e7:f3:a3:c8:cf:6f:c3:04:2e:6d:0e:67:32:c5:9e:68:95:0d:5e:d2 +# SHA256 Fingerprint: 55:90:38:59:c8:c0:c3:eb:b8:75:9e:ce:4e:25:57:22:5f:f5:75:8b:bd:38:eb:d4:82:76:60:1e:1b:d5:80:97 +-----BEGIN CERTIFICATE----- +MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD +VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf +BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 +YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x +NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G +A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 +d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF +Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB +BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ +j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF +1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G +A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 +AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC +MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu +Sw== +-----END CERTIFICATE----- + +# Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. +# Label: "NAVER Global Root Certification Authority" +# Serial: 9013692873798656336226253319739695165984492813 +# MD5 Fingerprint: c8:7e:41:f6:25:3b:f5:09:b3:17:e8:46:3d:bf:d0:9b +# SHA1 Fingerprint: 8f:6b:f2:a9:27:4a:da:14:a0:c4:f4:8e:61:27:f9:c0:1e:78:5d:d1 +# SHA256 Fingerprint: 88:f4:38:dc:f8:ff:d1:fa:8f:42:91:15:ff:e5:f8:2a:e1:e0:6e:0c:70:c3:75:fa:ad:71:7b:34:a4:9e:72:65 +-----BEGIN CERTIFICATE----- +MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM +BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG +T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 +aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx +CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD +b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA +iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH +38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE +HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz +kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP +szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq +vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf +nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG +YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo +0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a +CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K +AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I +36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB +Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN +qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj +cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm ++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL +hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe +lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 +p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 +piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR +LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX +5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO +dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul +9XXeifdy +-----END CERTIFICATE----- + +# Issuer: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Subject: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres +# Label: "AC RAIZ FNMT-RCM SERVIDORES SEGUROS" +# Serial: 131542671362353147877283741781055151509 +# MD5 Fingerprint: 19:36:9c:52:03:2f:d2:d1:bb:23:cc:dd:1e:12:55:bb +# SHA1 Fingerprint: 62:ff:d9:9e:c0:65:0d:03:ce:75:93:d2:ed:3f:2d:32:c9:e3:e5:4a +# SHA256 Fingerprint: 55:41:53:b1:3d:2c:f9:dd:b7:53:bf:be:1a:4e:0a:e0:8d:0a:a4:18:70:58:fe:60:a2:b8:62:b2:e4:b8:7b:cb +-----BEGIN CERTIFICATE----- +MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw +CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw +FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S +Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 +MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL +DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS +QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH +sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK +Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu +SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC +MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy +v+c= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root R46 O=GlobalSign nv-sa +# Label: "GlobalSign Root R46" +# Serial: 1552617688466950547958867513931858518042577 +# MD5 Fingerprint: c4:14:30:e4:fa:66:43:94:2a:6a:1b:24:5f:19:d0:ef +# SHA1 Fingerprint: 53:a2:b0:4b:ca:6b:d6:45:e6:39:8a:8e:c4:0d:d2:bf:77:c3:a2:90 +# SHA256 Fingerprint: 4f:a3:12:6d:8d:3a:11:d1:c4:85:5a:4f:80:7c:ba:d6:cf:91:9d:3a:5a:88:b0:3b:ea:2c:63:72:d9:3c:40:c9 +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA +MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD +VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy +MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt +c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ +OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG +vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud +316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo +0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE +y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF +zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE ++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN +I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs +x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa +ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC +4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 +7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg +JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti +2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk +pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF +FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt +rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk +ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 +u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP +4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 +N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 +vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Subject: CN=GlobalSign Root E46 O=GlobalSign nv-sa +# Label: "GlobalSign Root E46" +# Serial: 1552617690338932563915843282459653771421763 +# MD5 Fingerprint: b5:b8:66:ed:de:08:83:e3:c9:e2:01:34:06:ac:51:6f +# SHA1 Fingerprint: 39:b4:6c:d5:fe:80:06:eb:e2:2f:4a:bb:08:33:a0:af:db:b9:dd:84 +# SHA256 Fingerprint: cb:b9:c4:4d:84:b8:04:3e:10:50:ea:31:a6:9f:51:49:55:d7:bf:d2:e2:c6:b4:93:01:01:9a:d6:1d:9f:50:58 +-----BEGIN CERTIFICATE----- +MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx +CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD +ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw +MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex +HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq +R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd +yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 ++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= +-----END CERTIFICATE----- + +# Issuer: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH +# Subject: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH +# Label: "GLOBALTRUST 2020" +# Serial: 109160994242082918454945253 +# MD5 Fingerprint: 8a:c7:6f:cb:6d:e3:cc:a2:f1:7c:83:fa:0e:78:d7:e8 +# SHA1 Fingerprint: d0:67:c1:13:51:01:0c:aa:d0:c7:6a:65:37:31:16:26:4f:53:71:a2 +# SHA256 Fingerprint: 9a:29:6a:51:82:d1:d4:51:a2:e3:7f:43:9b:74:da:af:a2:67:52:33:29:f9:0f:9a:0d:20:07:c3:34:e2:3c:9a +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG +A1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw +FwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx +MDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9u +aXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWiD59b +RatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9Z +YybNpyrOVPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3 +QWPKzv9pj2gOlTblzLmMCcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPw +yJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCmfecqQjuCgGOlYx8ZzHyyZqjC0203b+J+ +BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKAA1GqtH6qRNdDYfOiaxaJ +SaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9ORJitHHmkH +r96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj0 +4KlGDfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9Me +dKZssCz3AwyIDMvUclOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIw +q7ejMZdnrY8XD2zHc+0klGvIg5rQmjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2 +nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1UdIwQYMBaAFNwu +H9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA +VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJC +XtzoRlgHNQIw4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd +6IwPS3BD0IL/qMy/pJTAvoe9iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf ++I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS8cE54+X1+NZK3TTN+2/BT+MAi1bi +kvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2HcqtbepBEX4tdJP7 +wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxSvTOB +TI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6C +MUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn +4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I +aFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy +qfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== +-----END CERTIFICATE----- + +# Issuer: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Subject: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz +# Label: "ANF Secure Server Root CA" +# Serial: 996390341000653745 +# MD5 Fingerprint: 26:a6:44:5a:d9:af:4e:2f:b2:1d:b6:65:b0:4e:e8:96 +# SHA1 Fingerprint: 5b:6e:68:d0:cc:15:b6:a0:5f:1e:c1:5f:ae:02:fc:6b:2f:5d:6f:74 +# SHA256 Fingerprint: fb:8f:ec:75:91:69:b9:10:6b:1e:51:16:44:c6:18:c5:13:04:37:3f:6c:06:43:08:8d:8b:ef:fd:1b:99:75:99 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum EC-384 CA" +# Serial: 160250656287871593594747141429395092468 +# MD5 Fingerprint: b6:65:b3:96:60:97:12:a1:ec:4e:e1:3d:a3:c6:c9:f1 +# SHA1 Fingerprint: f3:3e:78:3c:ac:df:f4:a2:cc:ac:67:55:69:56:d7:e5:16:3c:e1:ed +# SHA256 Fingerprint: 6b:32:80:85:62:53:18:aa:50:d1:73:c9:8d:8b:da:09:d5:7e:27:41:3d:11:4c:f7:87:a0:f5:d0:6c:03:0c:f6 +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- + +# Issuer: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Subject: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority +# Label: "Certum Trusted Root CA" +# Serial: 40870380103424195783807378461123655149 +# MD5 Fingerprint: 51:e1:c2:e7:fe:4c:84:af:59:0e:2f:f4:54:6f:ea:29 +# SHA1 Fingerprint: c8:83:44:c0:18:ae:9f:cc:f1:87:b7:8f:22:d1:c5:d7:45:84:ba:e5 +# SHA256 Fingerprint: fe:76:96:57:38:55:77:3e:37:a9:5e:7a:d4:d9:cc:96:c3:01:57:c1:5d:31:76:5b:a9:b1:57:04:e1:ae:78:fd +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 +MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu +MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV +BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw +MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg +U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ +n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q +p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq +NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF +8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 +HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa +mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi +7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF +ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P +qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ +v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 +Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD +ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 +WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo +zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR +5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ +GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf +5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq +0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D +P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM +qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP +0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf +E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- + +# Issuer: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique +# Subject: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique +# Label: "TunTrust Root CA" +# Serial: 108534058042236574382096126452369648152337120275 +# MD5 Fingerprint: 85:13:b9:90:5b:36:5c:b6:5e:b8:5a:f8:e0:31:57:b4 +# SHA1 Fingerprint: cf:e9:70:84:0f:e0:73:0f:9d:f6:0c:7f:2c:4b:ee:20:46:34:9c:bb +# SHA256 Fingerprint: 2e:44:10:2a:b5:8c:b8:54:19:45:1c:8e:19:d9:ac:f3:66:2c:af:bc:61:4b:6a:53:96:0a:30:f7:d0:e2:eb:41 +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL +BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg +Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv +b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG +EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u +IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ +n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd +2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF +VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ +GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF +li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU +r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2 +eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb +MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg +jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB +7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW +5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE +ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 +90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z +xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu +QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4 +FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH +22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP +xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn +dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5 +Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b +nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ +CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH +u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj +d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= +-----END CERTIFICATE----- + +# Issuer: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Subject: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Label: "HARICA TLS RSA Root CA 2021" +# Serial: 76817823531813593706434026085292783742 +# MD5 Fingerprint: 65:47:9b:58:86:dd:2c:f0:fc:a2:84:1f:1e:96:c4:91 +# SHA1 Fingerprint: 02:2d:05:82:fa:88:ce:14:0c:06:79:de:7f:14:10:e9:45:d7:a5:6d +# SHA256 Fingerprint: d9:5d:0e:8e:da:79:52:5b:f9:be:b1:1b:14:d2:10:0d:32:94:98:5f:0c:62:d9:fa:bd:9c:d9:99:ec:cb:7b:1d +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs +MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg +Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL +MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl +YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv +b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l +mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE +4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv +a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M +pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw +Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b +LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY +AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB +AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq +E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr +W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ +CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU +X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3 +f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja +H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP +JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P +zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt +jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0 +/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT +BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 +aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW +xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU +63ZTGI0RmLo= +-----END CERTIFICATE----- + +# Issuer: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Subject: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA +# Label: "HARICA TLS ECC Root CA 2021" +# Serial: 137515985548005187474074462014555733966 +# MD5 Fingerprint: ae:f7:4c:e5:66:35:d1:b7:9b:8c:22:93:74:d3:4b:b0 +# SHA1 Fingerprint: bc:b0:c1:9d:e9:98:92:70:19:38:57:e9:8d:a7:b4:5d:6e:ee:01:48 +# SHA256 Fingerprint: 3f:99:cc:47:4a:cf:ce:4d:fe:d5:87:94:66:5e:47:8d:15:47:73:9f:2e:78:0f:1b:b4:ca:9b:13:30:97:d4:01 +-----BEGIN CERTIFICATE----- +MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw +CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh +cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v +dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG +A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj +aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg +Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7 +KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y +STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD +AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw +SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN +nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps +-----END CERTIFICATE----- + +# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" +# Serial: 1977337328857672817 +# MD5 Fingerprint: 4e:6e:9b:54:4c:ca:b7:fa:48:e4:90:b1:15:4b:1c:a3 +# SHA1 Fingerprint: 0b:be:c2:27:22:49:cb:39:aa:db:35:5c:53:e3:8c:ae:78:ff:b6:fe +# SHA256 Fingerprint: 57:de:05:83:ef:d2:b2:6e:03:61:da:99:da:9d:f4:64:8d:ef:7e:e8:44:1c:3b:72:8a:fa:9b:cd:e0:f9:b2:6a +-----BEGIN CERTIFICATE----- +MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE +BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h +cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 +MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg +Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 +thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM +cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG +L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i +NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h +X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b +m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy +Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja +EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T +KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF +6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh +OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc +tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd +IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j +b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC +AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw +ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m +iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF +Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ +hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P +Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE +EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV +1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t +CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR +5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw +f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9 +ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK +GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV +-----END CERTIFICATE----- + +# Issuer: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd. +# Subject: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd. +# Label: "vTrus ECC Root CA" +# Serial: 630369271402956006249506845124680065938238527194 +# MD5 Fingerprint: de:4b:c1:f5:52:8c:9b:43:e1:3e:8f:55:54:17:8d:85 +# SHA1 Fingerprint: f6:9c:db:b0:fc:f6:02:13:b6:52:32:a6:a3:91:3f:16:70:da:c3:e1 +# SHA256 Fingerprint: 30:fb:ba:2c:32:23:8e:2a:98:54:7a:f9:79:31:e5:50:42:8b:9b:3f:1c:8e:eb:66:33:dc:fa:86:c5:b2:7d:d3 +-----BEGIN CERTIFICATE----- +MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMw +RzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAY +BgNVBAMTEXZUcnVzIEVDQyBSb290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDcz +MTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28u +LEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+cToL0 +v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUd +e4BdS49nTPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIw +V53dVvHH4+m4SVBrm2nDb+zDfSXkV5UTQJtS0zvzQBm8JsctBp61ezaf9SXUY2sA +AjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQLYgmRWAD5Tfs0aNoJrSEG +GJTO +-----END CERTIFICATE----- + +# Issuer: CN=vTrus Root CA O=iTrusChina Co.,Ltd. +# Subject: CN=vTrus Root CA O=iTrusChina Co.,Ltd. +# Label: "vTrus Root CA" +# Serial: 387574501246983434957692974888460947164905180485 +# MD5 Fingerprint: b8:c9:37:df:fa:6b:31:84:64:c5:ea:11:6a:1b:75:fc +# SHA1 Fingerprint: 84:1a:69:fb:f5:cd:1a:25:34:13:3d:e3:f8:fc:b8:99:d0:c9:14:b7 +# SHA256 Fingerprint: 8a:71:de:65:59:33:6f:42:6c:26:e5:38:80:d0:0d:88:a1:8d:a4:c6:a9:1f:0d:cb:61:94:e2:06:c5:c9:63:87 +-----BEGIN CERTIFICATE----- +MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQEL +BQAwQzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4x +FjAUBgNVBAMTDXZUcnVzIFJvb3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMx +MDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoGA1UEChMTaVRydXNDaGluYSBDby4s +THRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZotsSKYc +IrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykU +AyyNJJrIZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+ +GrPSbcKvdmaVayqwlHeFXgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z9 +8Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KAYPxMvDVTAWqXcoKv8R1w6Jz1717CbMdH +flqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70kLJrxLT5ZOrpGgrIDajt +J8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2AXPKBlim +0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZN +pGvu/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQ +UqqzApVg+QxMaPnu1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHW +OXSuTEGC2/KmSNGzm/MzqvOmwMVO9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMB +AAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYgscasGrz2iTAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAKbqSSaet +8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd +nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1j +bhd47F18iMjrjld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvM +Kar5CKXiNxTKsbhm7xqC5PD48acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIiv +TDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJnxDHO2zTlJQNgJXtxmOTAGytfdELS +S8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554WgicEFOwE30z9J4nfr +I8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4sEb9 +b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNB +UvupLnKWnyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1P +Ti07NEPhmg4NpGaXutIcSkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929ven +sBxXVsFy6K2ir40zSbofitzmdHxghm+Hl3s= +-----END CERTIFICATE----- + +# Issuer: CN=ISRG Root X2 O=Internet Security Research Group +# Subject: CN=ISRG Root X2 O=Internet Security Research Group +# Label: "ISRG Root X2" +# Serial: 87493402998870891108772069816698636114 +# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5 +# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af +# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70 +-----BEGIN CERTIFICATE----- +MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw +CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg +R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 +MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT +ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW ++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 +ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI +zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW +tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 +/q4AaOeMSQ+2b1tbFfLn +-----END CERTIFICATE----- + +# Issuer: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd. +# Subject: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd. +# Label: "HiPKI Root CA - G1" +# Serial: 60966262342023497858655262305426234976 +# MD5 Fingerprint: 69:45:df:16:65:4b:e8:68:9a:8f:76:5f:ff:80:9e:d3 +# SHA1 Fingerprint: 6a:92:e4:a8:ee:1b:ec:96:45:37:e3:29:57:49:cd:96:e3:e5:d2:60 +# SHA256 Fingerprint: f0:15:ce:3c:c2:39:bf:ef:06:4b:e9:f1:d2:c4:17:e1:a0:26:4a:0a:94:be:1f:0c:8d:12:18:64:eb:69:49:cc +-----BEGIN CERTIFICATE----- +MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP +MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 +ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa +Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3 +YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw +qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv +Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6 +lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz +Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ +KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK +FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj +HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr +y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ +/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM +a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6 +fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG +SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi +7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc +SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza +ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc +XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg +iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho +L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF +Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr +kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+ +vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU +YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Label: "GlobalSign ECC Root CA - R4" +# Serial: 159662223612894884239637590694 +# MD5 Fingerprint: 26:29:f8:6d:e1:88:bf:a2:65:7f:aa:c4:cd:0f:7f:fc +# SHA1 Fingerprint: 6b:a0:b0:98:e1:71:ef:5a:ad:fe:48:15:80:77:10:f4:bd:6f:0b:28 +# SHA256 Fingerprint: b0:85:d7:0b:96:4f:19:1a:73:e4:af:0d:54:ae:7a:0e:07:aa:fd:af:9b:71:dd:08:62:13:8a:b7:32:5a:24:a2 +-----BEGIN CERTIFICATE----- +MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD +VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh +bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw +MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g +UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx +uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV +HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/ ++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147 +bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R1 O=Google Trust Services LLC +# Subject: CN=GTS Root R1 O=Google Trust Services LLC +# Label: "GTS Root R1" +# Serial: 159662320309726417404178440727 +# MD5 Fingerprint: 05:fe:d0:bf:71:a8:a3:76:63:da:01:e0:d8:52:dc:40 +# SHA1 Fingerprint: e5:8c:1c:c4:91:3b:38:63:4b:e9:10:6e:e3:ad:8e:6b:9d:d9:81:4a +# SHA256 Fingerprint: d9:47:43:2a:bd:e7:b7:fa:90:fc:2e:6b:59:10:1b:12:80:e0:e1:c7:e4:e4:0f:a3:c6:88:7f:ff:57:a7:f4:cf +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo +27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w +Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw +TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl +qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH +szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8 +Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk +MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 +wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p +aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN +VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb +C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe +QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy +h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4 +7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J +ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef +MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/ +Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT +6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ +0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm +2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb +bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R2 O=Google Trust Services LLC +# Subject: CN=GTS Root R2 O=Google Trust Services LLC +# Label: "GTS Root R2" +# Serial: 159662449406622349769042896298 +# MD5 Fingerprint: 1e:39:c0:53:e6:1e:29:82:0b:ca:52:55:36:5d:57:dc +# SHA1 Fingerprint: 9a:44:49:76:32:db:de:fa:d0:bc:fb:5a:7b:17:bd:9e:56:09:24:94 +# SHA256 Fingerprint: 8d:25:cd:97:22:9d:bf:70:35:6b:da:4e:b3:cc:73:40:31:e2:4c:f0:0f:af:cf:d3:2d:c7:6e:b5:84:1c:7e:a8 +-----BEGIN CERTIFICATE----- +MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt +nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY +6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu +MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k +RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg +f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV ++3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo +dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW +Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa +G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq +gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID +AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E +FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H +vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 +0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC +B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u +NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg +yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev +HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6 +xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR +TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg +JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV +7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl +6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R3 O=Google Trust Services LLC +# Subject: CN=GTS Root R3 O=Google Trust Services LLC +# Label: "GTS Root R3" +# Serial: 159662495401136852707857743206 +# MD5 Fingerprint: 3e:e7:9d:58:02:94:46:51:94:e5:e0:22:4a:8b:e7:73 +# SHA1 Fingerprint: ed:e5:71:80:2b:c8:92:b9:5b:83:3c:d2:32:68:3f:09:cd:a0:1e:46 +# SHA256 Fingerprint: 34:d8:a7:3e:e2:08:d9:bc:db:0d:95:65:20:93:4b:4e:40:e6:94:82:59:6e:8b:6f:73:c8:42:6b:01:0a:6f:48 +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G +jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2 +4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7 +VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm +ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X +-----END CERTIFICATE----- + +# Issuer: CN=GTS Root R4 O=Google Trust Services LLC +# Subject: CN=GTS Root R4 O=Google Trust Services LLC +# Label: "GTS Root R4" +# Serial: 159662532700760215368942768210 +# MD5 Fingerprint: 43:96:83:77:19:4d:76:b3:9d:65:52:e4:1d:22:a5:e8 +# SHA1 Fingerprint: 77:d3:03:67:b5:e0:0c:15:f6:0c:38:61:df:7c:e1:3b:92:46:4d:47 +# SHA256 Fingerprint: 34:9d:fa:40:58:c5:e2:63:12:3b:39:8a:e7:95:57:3c:4e:13:13:c8:3f:e6:8f:93:55:6c:d5:e8:03:1b:3c:7d +-----BEGIN CERTIFICATE----- +MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD +VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG +A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw +WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz +IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi +QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR +HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW +BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D +9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 +p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD +-----END CERTIFICATE----- + +# Issuer: CN=Telia Root CA v2 O=Telia Finland Oyj +# Subject: CN=Telia Root CA v2 O=Telia Finland Oyj +# Label: "Telia Root CA v2" +# Serial: 7288924052977061235122729490515358 +# MD5 Fingerprint: 0e:8f:ac:aa:82:df:85:b1:f4:dc:10:1c:fc:99:d9:48 +# SHA1 Fingerprint: b9:99:cd:d1:73:50:8a:c4:47:05:08:9c:8c:88:fb:be:a0:2b:40:cd +# SHA256 Fingerprint: 24:2b:69:74:2f:cb:1e:5b:2a:bf:98:89:8b:94:57:21:87:54:4e:5b:4d:99:11:78:65:73:62:1f:6a:74:b8:2c +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx +CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE +AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 +NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ +MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq +AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9 +vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9 +lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD +n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT +7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o +6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC +TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6 +WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R +DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI +pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj +YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy +rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ +8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi +0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM +A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS +SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K +TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF +6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er +3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt +Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT +VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW +ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA +rBPuUBQemMc= +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH +# Subject: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH +# Label: "D-TRUST BR Root CA 1 2020" +# Serial: 165870826978392376648679885835942448534 +# MD5 Fingerprint: b5:aa:4b:d5:ed:f7:e3:55:2e:8f:72:0a:f3:75:b8:ed +# SHA1 Fingerprint: 1f:5b:98:f0:e3:b5:f7:74:3c:ed:e6:b0:36:7d:32:cd:f4:09:41:67 +# SHA256 Fingerprint: e5:9a:aa:81:60:09:c2:2b:ff:5b:25:ba:d3:7d:f3:06:f0:49:79:7c:1f:81:d8:5a:b0:89:e6:57:bd:8f:00:44 +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 +NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS +zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0 +QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/ +VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW +wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV +dWNbFJWcHwHP2NVypw87 +-----END CERTIFICATE----- + +# Issuer: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH +# Subject: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH +# Label: "D-TRUST EV Root CA 1 2020" +# Serial: 126288379621884218666039612629459926992 +# MD5 Fingerprint: 8c:2d:9d:70:9f:48:99:11:06:11:fb:e9:cb:30:c0:6e +# SHA1 Fingerprint: 61:db:8c:21:59:69:03:90:d8:7c:9c:12:86:54:cf:9d:3d:f4:dd:07 +# SHA256 Fingerprint: 08:17:0d:1a:a3:64:53:90:1a:2f:95:92:45:e3:47:db:0c:8d:37:ab:aa:bc:56:b8:1a:a1:00:dc:95:89:70:db +-----BEGIN CERTIFICATE----- +MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw +CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS +VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 +NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG +A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB +BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC +/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD +wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3 +OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g +PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf +Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l +dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 +c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO +PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA +y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb +gfM0agPnIjhQW+0ZT0MW +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc. +# Subject: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc. +# Label: "DigiCert TLS ECC P384 Root G5" +# Serial: 13129116028163249804115411775095713523 +# MD5 Fingerprint: d3:71:04:6a:43:1c:db:a6:59:e1:a8:a3:aa:c5:71:ed +# SHA1 Fingerprint: 17:f3:de:5e:9f:0f:19:e9:8e:f6:1f:32:26:6e:20:c4:07:ae:30:ee +# SHA256 Fingerprint: 01:8e:13:f0:77:25:32:cf:80:9b:d1:b1:72:81:86:72:83:fc:48:c6:e1:3b:e9:c6:98:12:85:4a:49:0c:1b:05 +-----BEGIN CERTIFICATE----- +MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp +Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 +MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ +bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS +7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp +0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS +B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 +BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ +LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 +DXZDjC5Ty3zfDBeWUA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc. +# Subject: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc. +# Label: "DigiCert TLS RSA4096 Root G5" +# Serial: 11930366277458970227240571539258396554 +# MD5 Fingerprint: ac:fe:f7:34:96:a9:f2:b3:b4:12:4b:e4:27:41:6f:e1 +# SHA1 Fingerprint: a7:88:49:dc:5d:7c:75:8c:8c:de:39:98:56:b3:aa:d0:b2:a5:71:35 +# SHA256 Fingerprint: 37:1a:00:dc:05:33:b3:72:1a:7e:eb:40:e8:41:9e:70:79:9d:2b:0a:0f:2c:1d:80:69:31:65:f7:ce:c4:ad:75 +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT +HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN +NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+ +ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0 +2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp +wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM +pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD +nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po +sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx +Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd +Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX +KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe +XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL +tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv +TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN +AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw +GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H +PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF +O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ +REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik +AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv +/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+ +p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw +MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF +qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK +ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ +-----END CERTIFICATE----- + +# Issuer: CN=Certainly Root R1 O=Certainly +# Subject: CN=Certainly Root R1 O=Certainly +# Label: "Certainly Root R1" +# Serial: 188833316161142517227353805653483829216 +# MD5 Fingerprint: 07:70:d4:3e:82:87:a0:fa:33:36:13:f4:fa:33:e7:12 +# SHA1 Fingerprint: a0:50:ee:0f:28:71:f4:27:b2:12:6d:6f:50:96:25:ba:cc:86:42:af +# SHA256 Fingerprint: 77:b8:2c:d8:64:4c:43:05:f7:ac:c5:cb:15:6b:45:67:50:04:03:3d:51:c6:0c:62:02:a8:e0:c3:34:67:d3:a0 +-----BEGIN CERTIFICATE----- +MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw +PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy +dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0 +YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2 +1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT +vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed +aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0 +1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5 +r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5 +cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ +wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ +6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA +2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH +Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR +eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB +/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u +d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr +PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d +8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi +1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd +rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di +taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7 +lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj +yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn +Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy +yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n +wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6 +OV+KmalBWQewLK8= +-----END CERTIFICATE----- + +# Issuer: CN=Certainly Root E1 O=Certainly +# Subject: CN=Certainly Root E1 O=Certainly +# Label: "Certainly Root E1" +# Serial: 8168531406727139161245376702891150584 +# MD5 Fingerprint: 0a:9e:ca:cd:3e:52:50:c6:36:f3:4b:a3:ed:a7:53:e9 +# SHA1 Fingerprint: f9:e1:6d:dc:01:89:cf:d5:82:45:63:3e:c5:37:7d:c2:eb:93:6f:2b +# SHA256 Fingerprint: b4:58:5f:22:e4:ac:75:6a:4e:86:12:a1:36:1c:5d:9d:03:1a:93:fd:84:fe:bb:77:8f:a3:06:8b:0f:c4:2d:c2 +-----BEGIN CERTIFICATE----- +MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw +CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu +bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ +BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s +eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK ++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2 +QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4 +hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm +ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG +BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR +-----END CERTIFICATE----- + +# Issuer: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD. +# Subject: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD. +# Label: "Security Communication ECC RootCA1" +# Serial: 15446673492073852651 +# MD5 Fingerprint: 7e:43:b0:92:68:ec:05:43:4c:98:ab:5d:35:2e:7e:86 +# SHA1 Fingerprint: b8:0e:26:a9:bf:d2:b2:3b:c0:ef:46:c9:ba:c7:bb:f6:1d:0d:41:41 +# SHA256 Fingerprint: e7:4f:bd:a5:5b:d5:64:c4:73:a3:6b:44:1a:a7:99:c8:a6:8e:07:74:40:e8:28:8b:9f:a1:e5:0e:4b:ba:ca:11 +-----BEGIN CERTIFICATE----- +MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT +AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD +VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx +NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT +HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5 +IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi +AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl +dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK +ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu +9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O +be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k= +-----END CERTIFICATE----- + +# Issuer: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY +# Subject: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY +# Label: "BJCA Global Root CA1" +# Serial: 113562791157148395269083148143378328608 +# MD5 Fingerprint: 42:32:99:76:43:33:36:24:35:07:82:9b:28:f9:d0:90 +# SHA1 Fingerprint: d5:ec:8d:7b:4c:ba:79:f4:e7:e8:cb:9d:6b:ae:77:83:10:03:21:6a +# SHA256 Fingerprint: f3:89:6f:88:fe:7c:0a:88:27:66:a7:fa:6a:d2:74:9f:b5:7a:7f:3e:98:fb:76:9c:1f:a7:b0:9c:2c:44:d5:ae +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBU +MQswCQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRI +T1JJVFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAz +MTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJF +SUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2Jh +bCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFmCL3Z +xRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZ +spDyRhySsTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O5 +58dnJCNPYwpj9mZ9S1WnP3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgR +at7GGPZHOiJBhyL8xIkoVNiMpTAK+BcWyqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll +5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRjeulumijWML3mG90Vr4Tq +nMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNnMoH1V6XK +V0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/ +pj+bOT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZO +z2nxbkRs1CTqjSShGL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXn +jSXWgXSHRtQpdaJCbPdzied9v3pKH9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+ +WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMBAAGjQjBAMB0GA1UdDgQWBBTF +7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4 +YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3Kli +awLwQ8hOnThJdMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u ++2D2/VnGKhs/I0qUJDAnyIm860Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88 +X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuhTaRjAv04l5U/BXCga99igUOLtFkN +SoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW4AB+dAb/OMRyHdOo +P2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmpGQrI ++pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRz +znfSxqxx4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9 +eVzYH6Eze9mCUAyTF6ps3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2 +YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4SSPfSKcOYKMryMguTjClPPGAyzQWWYezy +r/6zcCwupvI= +-----END CERTIFICATE----- + +# Issuer: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY +# Subject: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY +# Label: "BJCA Global Root CA2" +# Serial: 58605626836079930195615843123109055211 +# MD5 Fingerprint: 5e:0a:f6:47:5f:a6:14:e8:11:01:95:3f:4d:01:eb:3c +# SHA1 Fingerprint: f4:27:86:eb:6e:b8:6d:88:31:67:02:fb:ba:66:a4:53:00:aa:7a:a6 +# SHA256 Fingerprint: 57:4d:f6:93:1e:27:80:39:66:7b:72:0a:fd:c1:60:0f:c2:7e:b6:6d:d3:09:29:79:fb:73:85:64:87:21:28:82 +-----BEGIN CERTIFICATE----- +MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQsw +CQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJ +VFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgy +MVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJ +TkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2JhbCBS +b290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jlSR9B +IgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK+ ++kpRuDCK/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJK +sVF/BvDRgh9Obl+rg/xI1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA +94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8gUXOQwKhbYdDFUDn9hf7B +43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== +-----END CERTIFICATE----- + +# Issuer: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited +# Subject: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited +# Label: "Sectigo Public Server Authentication Root E46" +# Serial: 88989738453351742415770396670917916916 +# MD5 Fingerprint: 28:23:f8:b2:98:5c:37:16:3b:3e:46:13:4e:b0:b3:01 +# SHA1 Fingerprint: ec:8a:39:6c:40:f0:2e:bc:42:75:d4:9f:ab:1c:1a:5b:67:be:d2:9a +# SHA256 Fingerprint: c9:0f:26:f0:fb:1b:40:18:b2:22:27:51:9b:5c:a2:b5:3e:2c:a5:b3:be:5c:f1:8e:fe:1b:ef:47:38:0c:53:83 +-----BEGIN CERTIFICATE----- +MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw +CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T +ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN +MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG +A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT +ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC +WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+ +6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B +Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa +qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q +4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw== +-----END CERTIFICATE----- + +# Issuer: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited +# Subject: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited +# Label: "Sectigo Public Server Authentication Root R46" +# Serial: 156256931880233212765902055439220583700 +# MD5 Fingerprint: 32:10:09:52:00:d5:7e:6c:43:df:15:c0:b1:16:93:e5 +# SHA1 Fingerprint: ad:98:f9:f3:e4:7d:75:3b:65:d4:82:b3:a4:52:17:bb:6e:f5:e4:38 +# SHA256 Fingerprint: 7b:b6:47:a6:2a:ee:ac:88:bf:25:7a:a5:22:d0:1f:fe:a3:95:e0:ab:45:c7:3f:93:f6:56:54:ec:38:f2:5a:06 +-----BEGIN CERTIFICATE----- +MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD +Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw +HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY +MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp +YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa +ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz +SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf +iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X +ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3 +IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS +VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE +SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu ++Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt +8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L +HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt +zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P +AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c +mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ +YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52 +gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA +Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB +JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX +DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui +TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5 +dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 +LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp +0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY +QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation +# Subject: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation +# Label: "SSL.com TLS RSA Root CA 2022" +# Serial: 148535279242832292258835760425842727825 +# MD5 Fingerprint: d8:4e:c6:59:30:d8:fe:a0:d6:7a:5a:2c:2c:69:78:da +# SHA1 Fingerprint: ec:2c:83:40:72:af:26:95:10:ff:0e:f2:03:ee:31:70:f6:78:9d:ca +# SHA256 Fingerprint: 8f:af:7d:2e:2c:b4:70:9b:b8:e0:b3:36:66:bf:75:a5:dd:45:b5:de:48:0f:8e:a8:d4:bf:e6:be:bc:17:f2:ed +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD +DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX +DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP +L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY +t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins +S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3 +PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO +L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3 +R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w +dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS ++YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS +d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG +AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f +gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z +NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM +QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf +R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ +DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW +P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy +lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq +bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w +AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q +r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji +Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU +98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- + +# Issuer: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation +# Subject: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation +# Label: "SSL.com TLS ECC Root CA 2022" +# Serial: 26605119622390491762507526719404364228 +# MD5 Fingerprint: 99:d7:5c:f1:51:36:cc:e9:ce:d9:19:2e:77:71:56:c5 +# SHA1 Fingerprint: 9f:5f:d9:1a:54:6d:f5:0c:71:f0:ee:7a:bd:17:49:98:84:73:e2:39 +# SHA256 Fingerprint: c3:2f:fd:9f:46:f9:36:d1:6c:36:73:99:09:59:43:4b:9a:d6:0a:af:bb:9e:7c:f3:36:54:f1:44:cc:1b:a1:43 +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT +U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 +MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh +dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm +acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN +SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW +uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp +15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN +b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos +# Subject: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos +# Label: "Atos TrustedRoot Root CA ECC TLS 2021" +# Serial: 81873346711060652204712539181482831616 +# MD5 Fingerprint: 16:9f:ad:f1:70:ad:79:d6:ed:29:b4:d1:c5:79:70:a8 +# SHA1 Fingerprint: 9e:bc:75:10:42:b3:02:f3:81:f4:f7:30:62:d4:8f:c3:a7:51:b2:dd +# SHA256 Fingerprint: b2:fa:e5:3e:14:cc:d7:ab:92:12:06:47:01:ae:27:9c:1d:89:88:fa:cb:77:5f:a8:a0:08:91:4e:66:39:88:a8 +-----BEGIN CERTIFICATE----- +MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w +LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w +CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0 +MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF +Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI +zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X +tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4 +AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2 +KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD +aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu +CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo +9H1/IISpQuQo +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos +# Subject: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos +# Label: "Atos TrustedRoot Root CA RSA TLS 2021" +# Serial: 111436099570196163832749341232207667876 +# MD5 Fingerprint: d4:d3:46:b8:9a:c0:9c:76:5d:9e:3a:c3:b9:99:31:d2 +# SHA1 Fingerprint: 18:52:3b:0d:06:37:e4:d6:3a:df:23:e4:98:fb:5b:16:fb:86:74:48 +# SHA256 Fingerprint: 81:a9:08:8e:a5:9f:b3:64:c5:48:a6:f8:55:59:09:9b:6f:04:05:ef:bf:18:e5:32:4e:c9:f4:57:ba:00:11:2f +-----BEGIN CERTIFICATE----- +MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM +MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx +MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00 +MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD +QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z +4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv +Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ +kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs +GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln +nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh +3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD +0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy +geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8 +ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB +c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI +pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS +4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs +o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ +qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw +xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM +rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4 +AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR +0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY +o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5 +dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE +oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ== +-----END CERTIFICATE----- + +# Issuer: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc. +# Label: "TrustAsia Global Root CA G3" +# Serial: 576386314500428537169965010905813481816650257167 +# MD5 Fingerprint: 30:42:1b:b7:bb:81:75:35:e4:16:4f:53:d2:94:de:04 +# SHA1 Fingerprint: 63:cf:b6:c1:27:2b:56:e4:88:8e:1c:23:9a:b6:2e:81:47:24:c3:c7 +# SHA256 Fingerprint: e0:d3:22:6a:eb:11:63:c2:e4:8f:f9:be:3b:50:b4:c6:43:1b:e7:bb:1e:ac:c5:c3:6b:5d:5e:c5:09:03:9a:08 +-----BEGIN CERTIFICATE----- +MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEM +BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dp +ZXMsIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAe +Fw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEwMTlaMFoxCzAJBgNVBAYTAkNOMSUw +IwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtU +cnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4IC +DwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNS +T1QY4SxzlZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqK +AtCWHwDNBSHvBm3dIZwZQ0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1 +nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/VP68czH5GX6zfZBCK70bwkPAPLfSIC7Ep +qq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1AgdB4SQXMeJNnKziyhWTXA +yB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm9WAPzJMs +hH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gX +zhqcD0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAv +kV34PmVACxmZySYgWmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msT +f9FkPz2ccEblooV7WIQn3MSAPmeamseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jA +uPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCFTIcQcf+eQxuulXUtgQIDAQAB +o2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj7zjKsK5Xf/Ih +MBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E +BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4 +wM8zAQLpw6o1D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2 +XFNFV1pF1AWZLy4jVe5jaN/TG3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1 +JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNjduMNhXJEIlU/HHzp/LgV6FL6qj6j +ITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstlcHboCoWASzY9M/eV +VHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys+TIx +xHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1on +AX1daBli2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d +7XB4tmBZrOFdRWOPyN9yaFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2Ntjj +gKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsASZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV ++Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFRJQJ6+N1rZdVtTTDIZbpo +FGWsJwt0ivKH +-----END CERTIFICATE----- + +# Issuer: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc. +# Subject: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc. +# Label: "TrustAsia Global Root CA G4" +# Serial: 451799571007117016466790293371524403291602933463 +# MD5 Fingerprint: 54:dd:b2:d7:5f:d8:3e:ed:7c:e0:0b:2e:cc:ed:eb:eb +# SHA1 Fingerprint: 57:73:a5:61:5d:80:b2:e6:ac:38:82:fc:68:07:31:ac:9f:b5:92:5a +# SHA256 Fingerprint: be:4b:56:cb:50:56:c0:13:6a:52:6d:f4:44:50:8d:aa:36:a0:b5:4f:42:e4:ac:38:f7:2a:f4:70:e4:79:65:4c +-----BEGIN CERTIFICATE----- +MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMw +WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs +IEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0y +MTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJaMFoxCzAJBgNVBAYTAkNOMSUwIwYD +VQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtUcnVz +dEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATx +s8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbw +LxYI+hW8m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJij +YzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mD +pm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/pDHel4NZg6ZvccveMA4GA1UdDwEB/wQE +AwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AAbbd+NvBNEU/zy4k6LHiR +UKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xkdUfFVZDj +/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== +-----END CERTIFICATE----- + +# Issuer: CN=CommScope Public Trust ECC Root-01 O=CommScope +# Subject: CN=CommScope Public Trust ECC Root-01 O=CommScope +# Label: "CommScope Public Trust ECC Root-01" +# Serial: 385011430473757362783587124273108818652468453534 +# MD5 Fingerprint: 3a:40:a7:fc:03:8c:9c:38:79:2f:3a:a2:6c:b6:0a:16 +# SHA1 Fingerprint: 07:86:c0:d8:dd:8e:c0:80:98:06:98:d0:58:7a:ef:de:a6:cc:a2:5d +# SHA256 Fingerprint: 11:43:7c:da:7b:b4:5e:41:36:5f:45:b3:9a:38:98:6b:0d:e0:0d:ef:34:8e:0c:7b:b0:87:36:33:80:0b:c3:8b +-----BEGIN CERTIFICATE----- +MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMw +TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t +bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNa +Fw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv +cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDEw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLxeP0C +flfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJE +hRGnSjot6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggq +hkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg +2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liWpDVfG2XqYZpwI7UNo5uS +Um9poIyNStDuiw7LR47QjRE= +-----END CERTIFICATE----- + +# Issuer: CN=CommScope Public Trust ECC Root-02 O=CommScope +# Subject: CN=CommScope Public Trust ECC Root-02 O=CommScope +# Label: "CommScope Public Trust ECC Root-02" +# Serial: 234015080301808452132356021271193974922492992893 +# MD5 Fingerprint: 59:b0:44:d5:65:4d:b8:5c:55:19:92:02:b6:d1:94:b2 +# SHA1 Fingerprint: 3c:3f:ef:57:0f:fe:65:93:86:9e:a0:fe:b0:f6:ed:8e:d1:13:c7:e5 +# SHA256 Fingerprint: 2f:fb:7f:81:3b:bb:b3:c8:9a:b4:e8:16:2d:0f:16:d7:15:09:a8:30:cc:9d:73:c2:62:e5:14:08:75:d1:ad:4a +-----BEGIN CERTIFICATE----- +MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMw +TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t +bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRa +Fw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv +cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDIw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/MMDAL +j2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmU +v4RDsNuESgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD +VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggq +hkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/n +ich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs73u1Z/GtMMH9ZzkXpc2AV +mkzw5l4lIhVtwodZ0LKOag== +-----END CERTIFICATE----- + +# Issuer: CN=CommScope Public Trust RSA Root-01 O=CommScope +# Subject: CN=CommScope Public Trust RSA Root-01 O=CommScope +# Label: "CommScope Public Trust RSA Root-01" +# Serial: 354030733275608256394402989253558293562031411421 +# MD5 Fingerprint: 0e:b4:15:bc:87:63:5d:5d:02:73:d4:26:38:68:73:d8 +# SHA1 Fingerprint: 6d:0a:5f:f7:b4:23:06:b4:85:b3:b7:97:64:fc:ac:75:f5:33:f2:93 +# SHA256 Fingerprint: 02:bd:f9:6e:2a:45:dd:9b:f1:8f:c7:e1:db:df:21:a0:37:9b:a3:c9:c2:61:03:44:cf:d8:d6:06:fe:c1:ed:81 +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQEL +BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi +Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1 +NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t +U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt +MDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45FtnYSk +YZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslh +suitQDy6uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0al +DrJLpA6lfO741GIDuZNqihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3Oj +WiE260f6GBfZumbCk6SP/F2krfxQapWsvCQz0b2If4b19bJzKo98rwjyGpg/qYFl +P8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/cZip8UlF1y5mO6D1cv547 +KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTifBSeolz7p +UcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/ +kQO9lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JO +Hg9O5j9ZpSPcPYeoKFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkB +Ea801M/XrmLTBQe0MXXgDW1XT2mH+VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6U +CBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm45P3luG0wDQYJ +KoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6 +NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQ +nmhUQo8mUuJM3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+ +QgvfKNmwrZggvkN80V4aCRckjXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2v +trV0KnahP/t1MJ+UXjulYPPLXAziDslg+MkfFoom3ecnf+slpoq9uC02EJqxWE2a +aE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/WNyVntHKLr4W96ioD +j8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+o/E4 +Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0w +lREQKC6/oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHn +YfkUyq+Dj7+vsQpZXdxc1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVoc +icCMb3SgazNNtQEo/a2tiRc7ppqEvOuM6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw +-----END CERTIFICATE----- + +# Issuer: CN=CommScope Public Trust RSA Root-02 O=CommScope +# Subject: CN=CommScope Public Trust RSA Root-02 O=CommScope +# Label: "CommScope Public Trust RSA Root-02" +# Serial: 480062499834624527752716769107743131258796508494 +# MD5 Fingerprint: e1:29:f9:62:7b:76:e2:96:6d:f3:d4:d7:0f:ae:1f:aa +# SHA1 Fingerprint: ea:b0:e2:52:1b:89:93:4c:11:68:f2:d8:9a:ac:22:4c:a3:8a:57:ae +# SHA256 Fingerprint: ff:e9:43:d7:93:42:4b:4f:7c:44:0c:1c:3d:64:8d:53:63:f3:4b:82:dc:87:aa:7a:9f:11:8f:c5:de:e1:01:f1 +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQEL +BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi +Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2 +NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t +U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt +MDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3VrCLE +NQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0 +kyI9p+Kx7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1C +rWDaSWqVcN3SAOLMV2MCe5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxz +hkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2WWy09X6GDRl224yW4fKcZgBzqZUPckXk2 +LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rpM9kzXzehxfCrPfp4sOcs +n/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIfhs1w/tku +FT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5 +kQMreyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3 +wNemKfrb3vOTlycEVS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6v +wQcQeKwRoi9C8DfF8rhW3Q5iLc4tVn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs +5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7GxcJXvYXowDQYJ +KoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB +KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3 ++VGXu6TwYofF1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbyme +APnCKfWxkxlSaRosTKCL4BWaMS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3Nyq +pgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xdgSGn2rtO/+YHqP65DSdsu3BaVXoT +6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2OHG1QAk8mGEPej1WF +sQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+NmYWvt +PjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2d +lklyALKrdVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670 +v64fG9PiO/yzcnMcmyiQiRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17O +rg3bhzjlP1v9mxnhMUF6cKojawHhRUzNlM47ni3niAIi9G7oyOzWPPO5std3eqx7 +-----END CERTIFICATE----- + +# Issuer: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH +# Subject: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH +# Label: "Telekom Security TLS ECC Root 2020" +# Serial: 72082518505882327255703894282316633856 +# MD5 Fingerprint: c1:ab:fe:6a:10:2c:03:8d:bc:1c:22:32:c0:85:a7:fd +# SHA1 Fingerprint: c0:f8:96:c5:a9:3b:01:06:21:07:da:18:42:48:bc:e9:9d:88:d5:ec +# SHA256 Fingerprint: 57:8a:f4:de:d0:85:3f:4e:59:98:db:4a:ea:f9:cb:ea:8d:94:5f:60:b6:20:a3:8d:1a:3c:13:b2:bc:7b:a8:e1 +-----BEGIN CERTIFICATE----- +MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw +CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH +bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw +MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx +JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE +AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O +tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP +f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA +MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di +z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn +27iQ7t0l +-----END CERTIFICATE----- + +# Issuer: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH +# Subject: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH +# Label: "Telekom Security TLS RSA Root 2023" +# Serial: 44676229530606711399881795178081572759 +# MD5 Fingerprint: bf:5b:eb:54:40:cd:48:71:c4:20:8d:7d:de:0a:42:f2 +# SHA1 Fingerprint: 54:d3:ac:b3:bd:57:56:f6:85:9d:ce:e5:c3:21:e2:d4:ad:83:d0:93 +# SHA256 Fingerprint: ef:c6:5c:ad:bb:59:ad:b6:ef:e8:4d:a2:23:11:b3:56:24:b7:1b:3b:1e:a0:da:8b:66:55:17:4e:c8:97:86:46 +-----BEGIN CERTIFICATE----- +MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj +MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 +eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy +MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC +REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG +A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9 +cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV +cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA +U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6 +Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug +BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy +8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J +co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg +8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8 +rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12 +mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg ++y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX +gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 +p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ +pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm +9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw +M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd +GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+ +CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t +xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+ +w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK +L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj +X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q +ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm +dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= +-----END CERTIFICATE----- + +# Issuer: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA +# Subject: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA +# Label: "FIRMAPROFESIONAL CA ROOT-A WEB" +# Serial: 65916896770016886708751106294915943533 +# MD5 Fingerprint: 82:b2:ad:45:00:82:b0:66:63:f8:5f:c3:67:4e:ce:a3 +# SHA1 Fingerprint: a8:31:11:74:a6:14:15:0d:ca:77:dd:0e:e4:0c:5d:58:fc:a0:72:a5 +# SHA256 Fingerprint: be:f2:56:da:f2:6e:9c:69:bd:ec:16:02:35:97:98:f3:ca:f7:18:21:a0:3e:01:82:57:c5:3c:65:61:7f:3d:4a +-----BEGIN CERTIFICATE----- +MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQsw +CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE +YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB +IFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2WhcNNDcwMzMxMDkwMTM2WjBuMQsw +CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE +YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB +IFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zf +e9MEkVz6iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6C +cyvHZpsKjECcfIr28jlgst7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB +/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FDY1w8ndYn81LsF7Kpryz3dvgwHQYDVR0O +BBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO +PQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgLcFBTApFw +hVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dG +XSaQpYXFuXqUPoeovQA= +-----END CERTIFICATE----- + +# Issuer: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA +# Subject: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA +# Label: "TWCA CYBER Root CA" +# Serial: 85076849864375384482682434040119489222 +# MD5 Fingerprint: 0b:33:a0:97:52:95:d4:a9:fd:bb:db:6e:a3:55:5b:51 +# SHA1 Fingerprint: f6:b1:1c:1a:83:38:e9:7b:db:b3:a8:c8:33:24:e0:2d:9c:7f:26:66 +# SHA256 Fingerprint: 3f:63:bb:28:14:be:17:4e:c8:b6:43:9c:f0:8d:6d:56:f0:b7:c4:05:88:3a:56:48:a3:34:42:4d:6b:3e:c5:58 +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ +MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 +IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5 +WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO +LUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P +40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF +avcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/ +34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i +JkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu +j3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf +Xtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP +2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA +S9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA +oS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC +kHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW +5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd +BgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB +AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t +tGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn +68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn +TKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t +RCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx +f36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI +Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz +8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4 +NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX +xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 +t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd. +# Subject: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd. +# Label: "SecureSign Root CA12" +# Serial: 587887345431707215246142177076162061960426065942 +# MD5 Fingerprint: c6:89:ca:64:42:9b:62:08:49:0b:1e:7f:e9:07:3d:e8 +# SHA1 Fingerprint: 7a:22:1e:3d:de:1b:06:ac:9e:c8:47:70:16:8e:3c:e5:f7:6b:06:f4 +# SHA256 Fingerprint: 3f:03:4b:b5:70:4d:44:b2:d0:85:45:a0:20:57:de:93:eb:f3:90:5f:ce:72:1a:cb:c7:30:c0:6d:da:ee:90:4e +-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQEL +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgw +NTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3emhF +KxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mt +p7JIKwccJ/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zd +J1M3s6oYwlkm7Fsf0uZlfO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gur +FzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBFEaCeVESE99g2zvVQR9wsMJvuwPWW0v4J +hscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1UefNzFJM3IFTQy2VYzxV4+K +h9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsF +AAOCAQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6Ld +mmQOmFxv3Y67ilQiLUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJ +mBClnW8Zt7vPemVV2zfrPIpyMpcemik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA +8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPSvWKErI4cqc1avTc7bgoitPQV +55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhgaaaI5gdka9at/ +yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. +# Subject: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. +# Label: "SecureSign Root CA14" +# Serial: 575790784512929437950770173562378038616896959179 +# MD5 Fingerprint: 71:0d:72:fa:92:19:65:5e:89:04:ac:16:33:f0:bc:d5 +# SHA1 Fingerprint: dd:50:c0:f7:79:b3:64:2e:74:a2:b8:9d:9f:d3:40:dd:bb:f0:f2:4f +# SHA256 Fingerprint: 4b:00:9c:10:34:49:4f:9a:b5:6b:ba:3b:a1:d6:27:31:fc:4d:20:d8:95:5a:dc:ec:10:a9:25:60:72:61:e3:38 +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEM +BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u +LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgw +NzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD +eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS +b290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh1oq/ +FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOg +vlIfX8xnbacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy +6pJxaeQp8E+BgQQ8sqVb1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo +/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9J +kdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOEkJTRX45zGRBdAuVwpcAQ +0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSxjVIHvXib +y8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac +18izju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs +0Wq2XSqypWa9a4X0dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIAB +SMbHdPTGrMNASRZhdCyvjG817XsYAFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVL +ApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeqYR3r6/wtbyPk +86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E +rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ib +ed87hwriZLoAymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopT +zfFP7ELyk+OZpDc8h7hi2/DsHzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHS +DCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPGFrojutzdfhrGe0K22VoF3Jpf1d+4 +2kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6qnsb58Nn4DSEC5MUo +FlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/OfVy +K4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6 +dB7h7sxaOgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtl +Lor6CZpO2oYofaphNdgOpygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB +365jJ6UeTo3cKXhZ+PmhIIynJkBugnLNeLLIjzwec+fBH7/PzqUqm9tEZDKgu39c +JRNItX+S +-----END CERTIFICATE----- + +# Issuer: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd. +# Subject: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd. +# Label: "SecureSign Root CA15" +# Serial: 126083514594751269499665114766174399806381178503 +# MD5 Fingerprint: 13:30:fc:c4:62:a6:a9:de:b5:c1:68:af:b5:d2:31:47 +# SHA1 Fingerprint: cb:ba:83:c8:c1:5a:5d:f1:f9:73:6f:ca:d7:ef:28:13:06:4a:07:7d +# SHA256 Fingerprint: e7:78:f0:f0:95:fe:84:37:29:cd:1a:00:82:17:9e:53:14:a9:c2:91:44:28:05:e1:fb:1d:8f:b6:b8:88:6c:3a +-----BEGIN CERTIFICATE----- +MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMw +UTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBM +dGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMy +NTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJl +cnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBSb290 +IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5GdCx4 +wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSR +ZHX+AezB2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT +9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp +4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6 +bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= +-----END CERTIFICATE----- diff --git a/aws/lambda_demo/certifi/core.py b/aws/lambda_demo/certifi/core.py new file mode 100644 index 000000000..91f538bb1 --- /dev/null +++ b/aws/lambda_demo/certifi/core.py @@ -0,0 +1,114 @@ +""" +certifi.py +~~~~~~~~~~ + +This module returns the installation location of cacert.pem or its contents. +""" +import sys +import atexit + +def exit_cacert_ctx() -> None: + _CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr] + + +if sys.version_info >= (3, 11): + + from importlib.resources import as_file, files + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the file + # in cases where we're inside of a zipimport situation until someone + # actually calls where(), but we don't want to re-extract the file + # on every call of where(), so we'll do it once then store it in a + # global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you to + # manage the cleanup of this file, so it doesn't actually return a + # path, it returns a context manager that will give you the path + # when you enter it and will do any cleanup when you leave it. In + # the common case of not needing a temporary file, it will just + # return the file system location and the __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem")) + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii") + +elif sys.version_info >= (3, 7): + + from importlib.resources import path as get_path, read_text + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the + # file in cases where we're inside of a zipimport situation until + # someone actually calls where(), but we don't want to re-extract + # the file on every call of where(), so we'll do it once then store + # it in a global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you + # to manage the cleanup of this file, so it doesn't actually + # return a path, it returns a context manager that will give + # you the path when you enter it and will do any cleanup when + # you leave it. In the common case of not needing a temporary + # file, it will just return the file system location and the + # __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = get_path("certifi", "cacert.pem") + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return read_text("certifi", "cacert.pem", encoding="ascii") + +else: + import os + import types + from typing import Union + + Package = Union[types.ModuleType, str] + Resource = Union[str, "os.PathLike"] + + # This fallback will work for Python versions prior to 3.7 that lack the + # importlib.resources module but relies on the existing `where` function + # so won't address issues with environments like PyOxidizer that don't set + # __file__ on modules. + def read_text( + package: Package, + resource: Resource, + encoding: str = 'utf-8', + errors: str = 'strict' + ) -> str: + with open(where(), encoding=encoding) as data: + return data.read() + + # If we don't have importlib.resources, then we will just do the old logic + # of assuming we're on the filesystem and munge the path directly. + def where() -> str: + f = os.path.dirname(__file__) + + return os.path.join(f, "cacert.pem") + + def contents() -> str: + return read_text("certifi", "cacert.pem", encoding="ascii") diff --git a/aws/lambda_demo/certifi/py.typed b/aws/lambda_demo/certifi/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/aws/lambda_demo/lambda_demo.zip b/aws/lambda_demo/lambda_demo.zip new file mode 100644 index 0000000000000000000000000000000000000000..7308e9480a3552a54fe7157135de0dfac810640c GIT binary patch literal 1526024 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%0D<|9$)eVZe$D&ERwHovKrf|Id!R>1b^E4|8Ee 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

(o%gZ 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�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

W36v}+de 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%0DmeX*Xx$Ve#*4# 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

  • >c`Ysw_l=V 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

    Sy4ftO9 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 z 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�RW*s>`}7m?I#{YS}zwZ+4Pj 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

    a+7E+ZeMeTPRE%IUJuU3xr!jB=A7pg;$v=I} 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⪚C03<2bxXFM$Y7zGpN#edM^RAteme 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�eAszHEZPF~9K6FNUUBhqggcw&7vX!uG@D_=)-c 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%0A!sefgUFwEp(wcG$_f%IbKCrdlD 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
    )Pr;ExNPqk*xkHnCU3aO_56AvJrqsS3 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ÜH@tjYg2+mTaho z6e3m$NJ~TVA>}4hsf9ixmhks;8Aj;AByX!&pOLx_q64B>mh+Dz{+e$7-hc6e$pr`p z*%R-Y9sqcUbX#2bhweHeir>!mzZ&1D3zll2zLN~Z3V^4Ih*SWvde3caX1@8ev#!u7 zY(UoW+%O`_Qk7h5VSa-7 zyI|9G%%6D>73iC>HVPFIh#4`3t-NKaDl$}OtzjoBLWncOWS)>D^`S+iPD*z^eJ=E< z`20=bOx81$sqRPcpB^w@;DRB@mf&$t7O|q9kSDmVqyQ*T<0*su2E$07D0}X2ZG^xN zIkCFPj^d;!lIX9Pz}~suh9q2i3OJU5#+bW1CMz2z1ARfrZl-r+b|`O4tF`4PhEQ#Y zxQ!n4?M1qUE1zP1Qup(#A?4Z_01ltsz>tiJ*8XR8Qh-mbi1K9RLpQJt~} zgnwAKft?{aDqyFEG?S#&X-1Hvhw@F{d0NjnIM+>_$RJJFTN+c>P&h%QN0AnF{qY@? zytdmPu;>FFiaO483Ef~M21M>ocFqmfa*=pIG4az}94)&;H&%o{5ON1&yJQgp41(m7 z$*gUxcE3s)JIlbn+`I)CA$Hj5qFQXTb^Y>$77TS0CH!?BrxIqwiNAteK5n_UQXu7M zVjGd0U;D&&|%jo#>Oq3x^cBkgi|N6$MZD*>`28EVcv7?d%!;qkR;1F=- z&RDGed$i48db#@rrlIJ~Nu*cE7G$W{K#F9}qlDZHpqEpU-{4NoYS+tieNmqoCo^?u z-C^Dhja-L%5AJs0)#mTnmXVl(A^*s>-cFvBPyYwlKe~#`^t2U*EW!W>+7tmqg_6M0|+QS?ZqP)xYnJ*oki-h{qf zd*$lNQXJiO5ikX2&}r=lN&<)KD9SS%K>N$c(F`R*wym>hMYZ8^kMN=cM*<{;v#f4{ z9&-)C$)ULg+>Wa!gg-hor#4i*+Tcv3YU!)SV}nx@qP|%v&oMUuEA2>C7)WOYnV3D) zrH|S(@3P!Sz18U8h~|;l*U4kE?ehL9&N*hx^XHw1;1lz|ip}ZeXoivh%GP}TIU>>i zJJkH2c=JE#%{{8}wu4M?Js+A;*U3Vg4^VEA!gc(=c4VLxD%~vN@a@cH*PnOG)|`7G zL*w3%aL=WEZn$f!D~M;S(sg3J5h@*iktgclCKvzm5P@Uf;FNLvN84_(^}!fx=o%7_ zCzFdrqTn@zC>Wo^7?As_Ci{}Y5pNrq3mB#&vWIU6tim#PxvXm%t|erOnY^LsS#~wl z3vw3Xiy4^DM~Ht%nDx>%(!u8S;7I5_OC?0GUf*1u z+;UnNV!eP?)zzLFPVMa7OcVHsHPQQA^xNC8%(^{M;m>rTWPfj0L$Zz%JkWU3TR25P zVbe4|K)N{#?qdvWW;0BsSfZ&VZxbFj;s!u%iZ$r}ihpGCjPC9HdA$4EUVodzuC^!R!Ysc)j55b5}qKeFeS~1WDGgCP(stpeC z`&|{8DOm&?ehj#t0yH+9fQhj6L&k~(@jQ+A{F^Id56g!6I@ZH)gLBh#cIFbe*6hC7 zR#GKakcszE2hz$e`KPOZ5BD@+e^HqZPMt#mja(JamCp_}3C;Rla_`-X>(+A{n-m8LEVB(EAlR^**^dsu6s(7R!1Q3uU^8e)&@HDpmPek)rvp;^5 z1F7eO7V3t0N5r{xmA+LNhRte@2$v1|8Ho-~2S>Y_TA93J8E0(tWje93rxx{bPBaKY znlAgu>0~;avuLABqekNi2~x2tjm7@PPp$J(Z)v`rVanpIO(=b${pVzYepq|@^zsjF zkt>JXsP9$Lya$JFO>+yAZ;`;Pr2RpqN5HYP=7R`+09H>lTW|if1?B}OXXRXbzlIIj zux{$*z5V_%07O@S<4D^2Gqy<8c~w=ZR-UwS=q)Eme{B_pt3Jnmu>^fq}ohHH{#%7(~3*EIM z{~eRK{$4SV2MBs-!6a7@2K2WnU;0I;thS;DcM#acfeLZvFTjBdHJR7xhzL88EV<;w z2sgk$6)(DTklKR--?zen*Q1d|&endpY=%#T>N$GwS^^RQzpE48Zb14(;k*;lD^fKI zN~+lhb_BTGXdRkM!`2iV8+SVbsf97jFK(S}w(F06XkA(*{B1Q=>dxc*TO!v2Z>SNl z?!5~3xVJrlae^Z%k+bV_y6F}F@*^62n1^NOMBEy337=7!Ysx%$PViy6ThzrD0|%29 zH%!jZP8Xyq^M^#;M;_l)!vlpuh@B`JV;;Es$49X5^;svsSBmhyi=CX2Suf17VrAO~ zp1F-%04|c3By}<3_4^q?{6_{t1xaRCm+}F5>~c3XY0))O8hkwHz?aQ57d*btOFibvN98dP*qs&~QDWk- zx_y+AUGbM0dZ&^OVuPv;o}(++wH@TvTS?|Fwb{UQ(bdNFr7^cuSunKkWV*>lt^)Xh zPt&ut+qxaz-i$WTb|fDf`5xZH&>LXv^|fiq*$K+hBkzFBe{Dw~iHVONXv5>4De;8c z47llXwY54-OurXLPz6Wp0NqNSbXIAm2-ae2{D!*K7BcQq(!lilK_0{^v0_>9t6o?W z$t{*WlO8%($Ei}P3mnhFb@cQJvSaYsrDGZcN!ZLn>a7s)K_*k@4aEW#6M9;!wo6Ga z)zyB=<~C?_4P1jELtJk}KxVcnC$3L5`gUB+#Ts)l;x<%i?3Z#PQc!Jm!Rp{8zeN_V z%;3?XHYh$X5YETty^|Ce7bTOLRLuZovpF^jNAcW=0))q?trreS+*zC`Cy@$L9a53) z9wT3>JYJcxQI}VN`XV(5dQ6(9m*N_|TSsh+ySsn=KTftaX-=kgHWdlYwUskPx==I1qH;Br_GZ{QusjQLc(lzh<5?@~K?(R|wg}VafZjSMX8QH%1 zEZ&q7we8k5?cjf`G`OcI(K?jseAJM#FfAy6&6BhEy6}y2M!*PQKrTg{_>@c5+1{n2 z@BK`X__43oD{-VY7MED{HM6Jp%;8Q+CgdyJx6?g(;>k6=S}9z)n)_`Xt+I?dmUUtn zIS5#h3DPTAKCgpLIi>HzsO~@1b)-VdFzQSmAy`FdqC~a>5+f%s7pJ6*ON+c-M_GPL z%nZGd!D3b)qigrbK9I*guVpmuzu?Kz@5zskcbGZXpFa(FQ2`r9WFBSavt^DFRThh< z-p7RQFORw98s@wXvAGj3XISb?C0ZX%eGOc>8VGxV7$&$FOSq8XUV|0ww^1fugwZN; zRobfgw~`515bouFb~ZS~A^0RGNJq?|Pbm`y`b1^VVvo=I1+h2fO@)4E4{UIg>qlTX!9P9%zHZ%3p*%1iaS|b{^K3tgNQ>L@gU1KH z=?#yR4_Lj$%X-E3`-b?h=q6d>7(xA?KLUpc2nh4PqZ=1bQj}=MxZea`E1^wWG8;;ZhNcpvV8rU=_nh$7ZdKD!P%Gs9Sds{D57qZgm z;Qtb8TA4|7x?9|)>-6L7`rLni{umGHkHori8GhqPf-)jo6o7BjVVYgzS&ygz*EN=%>B%w@EWT4B* z>1Xgs|IvY?C+lmBA>(D+bGK!uFGUi~bCHvi_2r)6tdE{BoGuJoz-x;JU7>>0WCsH6 zz%#m%tMZ;8wBN|bo4ZR_tZ8%d4c!atiQiw&vq-F()gg?N>D_4C;HOU!9aSfjE^hS_ zEten-rI=E=Hkk!AN#6_rPLQ5a|0qpEPe6Bx72?#bQ6fhWEL3Uu8rJi5vTVCxBpRiU zN-jQ$Jsp^_;)>&mqzIaXJ|OVi^JM9?YE`0Z;Bw+m2bV*)It~qMOm7^R1$RtyltcwK8NOa#Y~&lP zP38|u+Q@@&!qTDjOU}D2#5fJK!X`a zN8z9sY!eWdykkFF5=a}-n5-J4t|-gBRA{UMmS;kbs$wAZS9TJdSWe||PIlvTjFUhA z1gwC;Rcei)qNrOdmw2!XB^=41{_G|)-WmbC!d;<^V(|1oR3~>QQxPB>-?VeXW^Yj7 z{2QNldC{f6Q z8Y(8mwA{q$%nh#!7Mf+>GJ+8ubVv`dQ{=t+ z;Gc6rr6H|`lisGzo>WA!;S?Lk3n9!3@1cOiuj5jc-U9bNVG6Ow+(bJ}}=~_=>r(+a%ZD1+#=) zMt&lR_1RucbV!D=k{7Mg z>egHcoAnMPIBlTL74<9l9EC?A`ac(xlp*y}l&n53FU|0eP7C;yQ(7p!U82n`g=}bo zCeF{T0LpB%rgw%g8+_%%yjy})xp(b~bH~U`*Y2iqLU3I_RQ=KMIqpSXMrP|V5)40t zktjk8!1H@_6qK@E2*PYa74{|_S*sX7JaIiCGH?vr_QyKALdZ_-==U}lX%SYpg(LB5 zR1>>Y#bY)bc>DjlsJP##dSfwb<=3mq|Gi4HOK=P5*<=LHanZ0Ig6NSt|o4+A?@mlI&n6+1)PyYQ;2Vw;CUgF|x$R>M$M!;d4+guxSTs zrE88KSQiTuz&Z$Q_RcN@164W}uJf?XL)M0fW1WCffCcEK-i1~&+C9W?4QhOE`tE-I z^&B5Y+))z>NUmLVVh(y&tigsCELRLCpi=c6ic02b&B*pzOzB(C3Ux(oZI1a3=d3aK z1d>hWUyvuljj7~mT!%Hf(su2X`x>H-sp~Je0_@HoXe9#MnKg=R9X&IQHN4`CCchb} z_`Ki`OTZl)&cT=F&a{TVbSvoQ1P-QTB zt-g8c>faBgiJ#xshViZs;m>Lk_SzPb{D}sy&}j21e(0`!-HXA24XhVb6VKY>+IGw2 zr5!1?JNpw?({C7`joe`NM?$UzKI83;D$lIn=FYKnV`D`2ZRYecq1|7sdYWgTzj zlM|K5CwD!K z>R}q*vKsTllQFS<3sb6NYow z1(Z3=g@CWvO>{8E=5n#2wLA%#f&xLuf``}?N(2TohG7}Q7t1s~> z!GZHqPWXuI2w)?D0Tj`%}F5(Z< zEcm};J?-X*(SzvNzxKFX-YH!KoHm^G3{f}6fEZRGz+iYg0~NSacf5E#ApHtQ2wFrK z3!H$uvS+*4M(+@a;u_XEX&SgK7rQPUW^)8X<2uID1o+BK98v%5O41jA<)3{d0Jv;r z=NZ@~=~^_C3N8D8e{Gb$HrBj0^hnD)ZJq5~UcFfb8*)-@fk<&queu-jS{AFNCO#~W zhEdcK30{55)^KB9S+ww%SWrMw2dR8`tn|U;+pH!t~9s7S?X5{Z{!-f{*95AZpn}tW6XTSC(XBS~t=O8Xkv`8VHBwA}+oAG2> zUK^jUtK3X~O7c-?oq}ad*`pZ%fc2)ypyerj`n1a6r6yxGbBn{h3R|;sMQ>8aT~hKn zGhKJT_Xek8MUu7{qjHz0Q})#%&%^!B!0N@1-i=zLyN=DJ!q?wS!#wj%uBnRj?GzIgp5kQ0`wHe6yg>UTDuE7JQGZsGYBo% z8*sOHyu5k2m2><1$n3_*{ho-FKu8{uo|>*v$p9J9H~bw?wRg%`Re9o*&f@bjV``md zddn*y(Aul5T$QHxLccEOAy#+Q6_2h*TxbNW0$hy8Tz?`2r5d3-*8_b_>Rh|N0^sUP4&BXDc48eLu9 zRyK-kzbI2eIYxZhxAZadx64MGbR#kyKYPo;tihOF%bZr0iLeJ5+Upf}KYIsJa0@9% z-YOhqdlh~}%iw(njH4|TB!SM^>s;z&VMv9#z&`ELcHt91U+;(YMCUtiguQ@>4=tz8hMEawKB%`a;Xm~v{m|YqouI&_3Hj+4wr{w&s zjzBD}m55xA-DC_cHeepj0EjlRhiW}0Ji@p~iClw)TA~fz6QdDAr#u@FhyKPtWvzB3$82`83V^!Zdg95}==YSxl=QI=`7Apbyj)_#P~BB>ee) zEYZvJVW3<-luYO~oMN3tAm%I+i3Q{QjRZ(r8juY}f&jvI(l1mfD^sLFlfRy9=|Fqe z5W1k7yNM-0wMDDz-?V|MD{XGbdJ!JzN}GQo1}_W1q2{?e@wHxUl4<$uE!h@}EzTLF z2_yQSRp$?ThlvuNcEhmxZ4}bC%Q4+Xq2 zpdz^AmH_ttJN41LzdoU{moVFH)x<)fVbumPxe3)!EeXbX`Zd2m;N1W)0nOh;XsU;c zbJBV%rLI!yYW~=6-AR}5U0z@5zz#^%=a!z6xTtLX3@c_|Ju0cJ!kG2<7_i};X-eiA zNYho?NOk7y(amVM0id>>P%vYl-E`zxZ*&#yusM7SRsGysCHb;?Y!G z=@-KF1q2RsMXc1QW=Zr;de>}~3rbwL#50-W9}9_h7wI>hNcqT6JHlC!C&QKr3ZTil zLyA}7?XZ{DeWU-_>xC`Bw>f1l1?ISPrT z&rNrlETq*a1#LKvE&{{4w1+;vVnp4|CN6j_3(NSx7e9J7)3TA&svi);&Z@l;zfR0c@_vqkVa(W?Kj2pN)}1&H@?ub~K!gy0pC z_XCV0Y*uO3VEX5G7Jk>+&QMUQtLarL^0k!yc+_vQa5SK3ts++%=SvJM>jFt+TLZuX z6yb4*^JCdq_tv}^LKdHujPkjK!;0~o=j5eZhIwq*xSPeO%`m+DK?S69c&|JJIP@=P zPD-x(@H8J}8XMJF6@!NlS#=pI;`es(#<))t<9s>{<+#{17wB1Hc`dOU=a)|=59fde3@`lUQU^7G z|3TR~28j{`+Iel;wr$(CZJT#(+qP}nw)d`WGdD?9QZIRxSNYRFXQryBrhEFF?;K|? z?CIGBq9`a5sFA}QHT7i!;vl8|NSv!_zA!9=4;5O)caF?pJ6x9_V%O}%Bx@)=8=s)! zV2TAfHHBkavF+@HWf=;qgb1`Iiq@)4Nt$I7YkQyvM#KdS$-d%URD5wqd5(L<5w^*A|u!;M9CE)^-bxKQo%UL#=R15x>wFC(o=)658G#? z*d3G;5BiLd{r(+ITbiy`xM$B1a(qh+H})up566KKA%PAU%{cw(J3sK%JWrDein%ya679tfSlbGrGG+fr8ra(XB!5$t%Tkc`i&_ny{ z%`foYVyUQRTYpn6EdGVAebNTU)wa4ac*a4SyTS0XiP3}?ad~c~Pu$!|MY4Moss(DR zcfGY5l&dg&ZKUJqB^M%A4nubI0Np))&RZ8Qb2zGL9F4Ut-%PjfxMev0a^9Ft@#dK4!)bzLv@0zpYrpM_4es2hVfd-`+jpu0?e;xr66g$uU`ej zzNgRklSshkVvsp+oS=z z!GBGJx4x0#x@;8(jgN5AjIgkRq8oKDVf?0=t}F-(<~K9;|UQ;)QzDVz-`rR>C9E@cpHF_Wr~88CIF(g)9j@Tqkrz?X= zEM}zqL^$!M5~chD7V3E7D^UMx-OW^n7fH{y$nF9e8~Y}Ghqu|aNv$PXvv$mSHA@_) zQYlIDvP>G}mQ=;JjcTc)_*XqHOJ^pUVLiX6Mf;jG zg$u@AhaNp3=16G75CPH_V}2&ISusDE&NH)NW|zkvSZd^-_1 zlaqr20MPx*Vf(-4+sX8wcizs~^uO@H^r-LI|MN8bdDVAxz%v4FRjQ`X0kb%h8!J`> zBpvUj+FI4UiWU{2_uByQA*`XC{tIDUb2$QW#*7#D{kjG9=ndTkr5geUVsnIsn%p?* zNqftvg-io7ZF6*umYi(i!McBc+B!HEDknV4*SK5r`*-@szyoQ}pWD)Se~DX&DfKJ? z%E=J1_O*8Lf)xd;hRfApVL*4?Hw`xaPe(AdHFt2*ZJ+m2AG?Hk6-VjSeo$MNZxnPq zb_whsMK8MLKX`0&0vzTi!xbtTKFl8_t=0Wg%T+X|{rL zvA_nrtRNbNyE5dCMiMiy*r~op>a-M)kpEqvQ;Hks7=gE_SAMwsu!b@-8$wRNY>T84d`){ZG0bLHy4q6DR;UcMBz!m-AcG6_DKh$Z6Qe{7#2th z(^$(xDY$7a$Q1}fj)c?0NB_=;glh`qJD$Q|NoW@(p7L#cb-LKP=;k5SY>gKFbkT^TZ^C>Li4lLZ?1crwV)-E$jNh2-A`V=&$DRQf z#WLJ^wn*F2$uA`OUP)SlL2RtS4g=@Qc853ONvDbi{HBsMf+vE96#pqPj;U(1dJf?fm zxymia0@fJLeLA;ezDV|SXG&NXVk04(DjU9LAgmk@A~Kv|7{mM&)mBju^hnaV3!Bh2 zO02nPLqMw}t@}vK3ddtd6LC!Ha}J8K`y9suK15B%E6Iul$NG`aj04#i?mjTVd^Dpc z=eHRYs;@6zvG3K9ZhX&gBN(@JN0Lc*__2J+3PkUuVJ--Z{TWU+Q)3hs}gU7MLif89(Pq3|HS? zZsV8o*IhuWsQ%eN0@Ss)@8f%D1ZZ~=rJ$0E<*!nN_Krb`J4^R?s0-y+x;cAIg7)9~ zTY@Tp&!jNuKK12ei4w{<8aqy%6p!y?Z{==#T!yAv+XV%5Ejq*Lh#2wz2|)et@Jpi> z8W|*X0Dxi1|HMf4k6ik%>VB_ztev;S68GMzOUvO&rch`iTG>y;I+Q86R>cZE5^=~c zodgLOP#S;$X~2?)r5^U`x=sTy5sHsGdn#+HimGUU($CM&*PfuW(Kiy`e|%JHp>95D z^iDJt8wq5am?#cuP-{{<+aQLCBFs;mFoQq5$)4B~LSDOB)U6-$bxs|<%Ifffh(e?n zG7mqRIABFE#=M?YJNM-!dGy+0#~$bC&rj9`TNKp+iho3H&mwN0IS00B(T zBt8D?$JW!`)rqB>tNZ(s_EJdmxE8i+_-S2ry!XY1`w8&+0n?u&%cnCK@03FRC7qq> ztM*Mk8Q42e%il0x&O8NY2RszaCROC%d7HrFIH zQx68vS-pje^Q`HO*<<_qQT=%WKsQYEFNM_I{%B=k%l%rf?%BL*CGsu%z2VN>^P~^6 zo0se1^d32`vl*(M74W7^N$#6PgDQ@d}) z_>;e0GnG_0bK9=1j^j-B-{}`>nQmO^r4wK=uE%Z905zC9*zVr}>iM#8*|jCs&nWi0 zUhfe+^zMBSk-J6_G?m0tS|-HQQJH0o9Q8&!rFTx~GZ zc7bK!fFZ~~9S~9xnZe-UB$B?oA*@op7`=ad!514cJ?p2|@@oiGfgjn?j7CN-i-ums zy`OL;0UciYxVgUU9G-r5`U6-Z;r(^^vFgt>KJ2FrcU~PJ7}9Y^pENVj-^Al?h{@_V zDF=^6;57#FS?x2rAU~6kzGy%sipHL^w7&~$RvI{Hn!f(eI)=Nh{eX-9_n&x>nZW?q zm`@79R{{Lv0b$_0Ex>?TLAtpQobsR&tM$FH&B-Y@K^6DPaO0A$^(_PR{zQNnY?cWA z8Wji;WOb%6R`OpiE!p=*60J{rL#bB{$N?YTh}9bl0}J``k-(pa0s9G~{-N1pO@W6M zOz;OFutEdB14D@+-4utOIAEmUAs{76{!J*&>Pj(xM?rA#v-?E8A5;^&@o;c@5?GLW z3yC45V+wOAf(lXEt=fDc=Mehb|5%Fi!;Z82n7l|4 z%{~>FQmw5iUUO>MBnsKgd#_T-j8|>4yPFpwflb>FUdjqW-r<(fYq)_Q1D6j2f1}TS zwQ_latdtixp7{6nl2(;VV9lQevE)k`()8vtxy1-wYmLJI;o5Q;Vxtt^o(cLX?;gK+ z<#!pB7WbVd03mSClJQF_l8?bOsQ%fY?*>r6C5rNR)|g!-w(BPmD|6Intwh8VpolRp z1>>59KtO* z3Aq7zUNG@H!5H-0`!Znt+`D?(9)w-COb+B4_%Bxg4aS->Ee=AY_zZxO=UVlH{dBF* zZJx$ue~Vs<*YCR7XPM46(vHqCyB8<$7Cm)z>?_g^zg^$7A+U@v9Imw;)a+O~5txM6Uv|a6 z-wMAwm*Y4=1#ar3o8(A^CewjKT;SJye-IoruMR160W4R}&|l66slY~+4-~}J%4#5O zBZz0*M9kktI*gW3A^6U5D2_Y_JeK$ZWv>PBZpaWDi1Xt>TR(1d)os2x9s294UQXX- z9RyT1A1zZ@d+drZvU~`wMCXd*8A~6(?l=*i3_gRkhReUHI_cTZL4tL zcwtIV#SG&Fw|9$Ekz5A{AAp%|(e*t!aNft#{t;QA(fksr$5Q3Aj>-t|Z9g;!Ic4{r zh<=~tx=^1l*|_C(uKLuaT$#l`PR8sX(K@bFWZD4?D06Me^mW2?AM{>F0yffO1b}J^ z|CkSv`jn5fugD6sLlU7ah8Jl**SNrv#IGUIZ_vKozRvwkhFcWsk)jy#p9DKF*GT#V+g*msV)Oh;jI!?}Pp7qHl1q>d+`d~m> zr7gl{-u>>q$#(zsspBc52_=p!H+26}I9|LAX>e*SsHktU8IcR={UaPF(1+pfLgu-o z8c&_mC`&#!v<6Mo@m`heX?;GFrSWv)b0;@J$b{Uig%JSc{nW~AEpTyfxa3s`$(`oM zxKWjhr+5SpoBuf9TOhlY8qC)XJZQ5|o5G|1*6w!BZLE}GBR4RL#uMNd4jla}uMpY_ z0y>}(MiQg{i(u-90j&(GN2kk~lu+^z3B{c{88kyW)B%MoBpH_mI2yymrG|x1960<2 zd|==_PBi|rk;VYz>Nzn;93K;PDm+>24X%uBaL3Y_R%cuP;kb^2(h~M{HG)oKu0Qf zf=oP9t-I1aVI*{XsmV3Nuvze_12$|uS~6G{VH3UyPBzQDm9EJC9<0BOJ(4C6>;p+g`Pm>HTb!ge(c+Yi87VcGWi7Hr$LMQN&6a= zIjK#qDwlJ*)rT^XwXkPjj%b$Seji{Mj8a!eu@4=D!X(`>A%I$424mzCVY%>xZ%WmZ zFy^sT(WOZM>2n0?7}dL!R}o~7s|Xo&>=xlLCq*3rcwr%S34MLn2#qCdD;MBv{U%#{K{Zo$|jb9f_=B)kq*4 z#bMC&EQqQIQ!u#PSn7o%015eH$wQKW0+W^JbN{@~TlyG`7&*>tUq&dN%x+PiwtnMn z8sU;s&C)(| z1l>)MA~Iz`@KbYGY9 zOXtTcGjGjB)airSn{Nw&Es{Y}Q#1Q>&~)mv1l^0o^+HkLPf>T&M1-Wol{`qm z=dA;j9j>WsWW`cXh%Nnoi-F~0`&2l{4q~DxF4P!(J=bfgtweSnXWPw4U~dqpgMVn8 zfmm%?1e+v8=|FeDQX&>Q2`&?Zt$M_E!mA*~;$lxryRjRA~hAQK6D~ zKC2|3AJ>7}NLL4i0-Dtfovp2vV;xYj*H1ZV^@ftPo z)Fg3I3mdVa4l_|jrHCI~ND8kvnjK!^Gu?$`-a#uC|0#g(q*=|vabeQ`x}j9s*vr^+ zVZzPJ02gAR^Vb?F4(TrNM8kn{;+XADtrf9NXIqJgRJU?eU&iy<4>cQVL(BQTdB9bW zy<^>v9Vx9F1X--he9f|cnj)+}+DPnRsaP4`)C-mPLs}W-lr&aCDh6Ox)WY{;eO`P> z&$@xV)gX>>S(^&M<|D}@Netht*K5cAB*P(6c;hT00VG#`y9~3vVfG(jU3t$sb2gtq zOOX2)s+_vua=DJo0B;7=ZGG{4E#}+TW=nr|32$So%q{{Pw>E|ma|prlxV>c0t=`!8 z53$}h8u|KEGWa{@9h0$v2bp8;l79t|kBNViWvn|RZ@8uEh@LlIka2IJlODRS>`&rd~1Dc=NJPO>Jt&QY}$yvCoG?skHg;Nvs_U+N^YQesR4 zBO3py8o%kVaSP5$hm0bQ9XKPRNY!{7CRg-_T(Kc#^Z#|US?jdAgkjK!;Q@ezK!V{m zht!AaL1W|YMYzj9;Wb3VWu8XJvX+M4!)F-c>f{Me+l8*ft$b!wS<%aPPY)fUL{I@{O(d!5E z3wPWTJM?hk#O&+M-t|w+&9+YUWvd0CGAjcM-sI)_hto1?9SNYUSH@J}c!p9qB&!!F zB>^s8yIPs{UKmtNdnfs>^>Dpl!)3uVQTk4ASGV%c?qr@&#yS+G)f}4MPD!Vg1<1Wh zzCFB9M65z{iDtV4_NK`y`;}xZC;j&L!1m|+vu38s0SB2%HFkMs?>A=Ld3+$C_v6B7 zZVFuFjrHZ||2+M@^4Vrn)i>WfSv_LL$FIERK;CBQnsjT{`mJz!*w;swL=#DN72g}D zBB^lU_Q9`I_v3jN-!#<-VsK(U$~{%3d^^o$FFC6M1YD86p!9gu!2=c3YK7aO^vuaTn^(G*0tu0 zlXkJ~jkoYy3AzRvVr_5=hyT&X`?;M2?hUEW`DW?&gQ-Usp^$;+CSp3TCt>6`6>leDnlRz(x?j&cJW(Z@AR} z1r-jQ`7!hSVGgB0uI61ZTg-D1C?u9dg(SqTi|%6UkjkY=VCpTSRz0x+*m?j|g!~Fx z$zduTACJLfVgNHZlq$?13#3d6mT3qsCNPP53(?BYH31Bvc;`!6`hha!Z>M>!0w$$< zko=ly!c>X8liNf;27*D5)&T|oOpJ>_U359TJ>w!|-2f@yV8Gr`dI+uJ48c^cQ0@(i zi{G}Wq>6&>xo+9oSU`lm3kWb@!=#FYNs~5- zn~Vy`LCQC)t<7-;N}wf&)|N@GW(cR!x5i}5Za022?x-Ph(Pqo&a}hBw2JP2@+@AHT z7j}minyvGhzK-%uD8s7yIYUlnH>oC+YPsOmf|f5U8zK=E=u>Mk5P^*xjA+A6s<2)e zg#`zULiJq>r(#PBpEDc?Bq;BYWfE@$tgL%Rw3Eydx`9mpgk%PhLRy1JxF2S>f@w}S z)vzORr~|{C>$l*<>Nri4x#+i9HHQmgn72oU3^pY*94d<*YFdSVIAD6`-UzcuG@qoL zs?Nbku$4;oM}R(}$VT}%9&_($AdF*?-NJQc`&zc;I)UZg6Olh(G8EE|DPd!(0xYh4 z$JuLUhe5NjJKzNFyoM*R7Hn;8tF_pr=-8V;pI=g$Q%js z_(H#y#fbo3)ZBtza#jrU5UsMn!8vYcczD3?{` zXP7!(bamD6@}vWsuSXCY*iWyq6{0+6rchOo171~I-o~Ed@~oWb%mh_c9s@>WoR@6% z77r-F&bB2f=476m0(ixM!eMFJRn^+5%+jP-P|ZrHrf2{2&poez`=H!QlEEnQarvR5 zqZ2O|cTpxF#+0Vfh9@OX1T8d2Y|#@;~fW6q?vB$<-48EZ)L$d*lWJ=D;!4M-G1ykb)Q|)H*SCkG{3a>)B7lgFYqhqM}C05ITnuh69Hr5 zz^-wC#u2~lo}s_1hM^74huIj6Y$q5@Hwc&h3PcfmGKRqj68lPMCjj5 zB*Z!6zn54h9*uY0 z)h0=CbcD+4RJ^rhU8@(J{uj%%{S-8WkwTX76xG@95+b_Ra!I6IJvFSwxP!wPZDylR zuBDgdjN3$Km~>R1ga$+)$)~U_9a2Pa%vnc2=j}Myq6%Y&uH{{m8@7!^1#yS`h=Vr{ z%?-Hok|dtf#F2a|sO_U|OVlbJv0S?c-*N)s$w+?_$m&fS1%(v%pv1S_u~}yzCrC#T z!T<@rOKC3bNjh~rl(Xre^0ocM}8RCNnnR{N*CieA6t`;G+Hzkw!S++^$|qG`V8`sWIb^LfYzA z;vC1k_`l;V-x;Tz6g*uqYu+$7qNp0xob(7)Tymyb+w^s59c}sIoPXr2GId&6t*-;8 zDaVwDt(UW>sdFTsw44!NLQ7*>cx;-ClO@$*^#QCom7#W2#1)vPTaF0a*8B%S0&!|i zJ{)JpPC5KXYq1vG#U~M7fNadb+$VH6k(G#Ib6Wt%y|SJ5)6{qF--q!Uskv*bx$8!! zF0?50q~3VdO2^XDCm&RAq%9_@NeNX_YBu5unmTA4MX#@gR;{B%3zBl5ng9!l#!%}Dq)qvBJO6nwy5hd|!m*w8#u-lp5)m*nSY z#anHB??;CnoibC}ec5BiHeKsFK=U$J;D8<02ncE}w}7Re1E=S+<+1V)d2c7r?oCjA z;KKZcAKtyB_5-}ETHPDPI~|!wzGS>Hs>w~H1(NNGeRXAp+0>Aq_W|^NkL`D+1#gd; z2;XH4ckqS=Q|rB&h@+K*8k!i^5`>D@`Rw89i0d<{-K6$YDS9=VRjSJkl~SjSB1;2@ z=n6^xq$0{n7@!gEeb*bTP&ZD(>_))4)vI;5ib8y@p)ZY!glFhsOvbWFgA%Zu0|M(5|!wKy`GN=VA zN}uL1i@*Vty8!GW2Gq+C4P*>V!`(+tJtBl~HF6O12z%-nVKCm-j>WzE=<2QV;==Xw zBV_vQJ9Iis7EvpMH6;B5c!@;E?3m8yU2PJQR+Fv*JUld4v zda?NA0Yd{Q&(y-&__xpZCVab86zf!0s7?Py-i`xCZ)P0rtkstYGR*mo-44F#Q6B{D z6wK`Tf~k37rBB#l?o(qqwumY(sk=_CqT9Cnb7a=iYF3pNu^MXyYq^K%-txrkin34_ zC73pG)@tHuN6+TY8yu5>^ry-_ww7mHYC>m)_O0^_uiqj1&vAyXIq!<68BP+}AuHZX zd}YRFzaXkAxPT}y>G@~Psw`(zaw(&k{eUwpV6Xoom!{_ZeXU66i;Z<5S=AR0>wc@Q zo>g1ksN~W{GyDERk|H~etqqr&$|0uBi*43hmlLEZx(nc92S2i;Uy$Whiz`;j!09(E zRZ2hDn%0y36T-|%F1sHWa;k55tRfY*oMfb8oc*-Hx$yvKb?iJ`ZP1EO#Y|nB&@^+I zPDUOp2#j=qJhn3b?sWRROYow#Uo5Q^&pfemAhKw zfe_gkE(`d9eispN_Rr_JozS{>NKZy6vxp2;}5IvKKkV(u@ zh)Senl6?`N1S8FO5~{%bpie^hzUdiApgx*!nfJ&x88=LKMm~ZU-X&_$lUsl552sNa zH(mpB^&s0(5FwRkyCRWvIzoLQueVFIII>Ldh&eL>cyKF+7upqsVFK|aUzQix6@4(l zc(7v<&JF4YXYj&%nDKw6;RI#o-Pe4k;#{h~K!YE~!Tbgc_k0i{1}QnJDi#?EXC4y-M$lK^8u6kLRL zBqF|CLIMp45Ykdbp^O>aToW}ZLYjp1?9||@;{%a8#jv9&hXEw4iHXDge4Nrn_TU}1s+=Rm`&>CJy zOT`U)FgSRbWHvgfQcUucFiVz874X1%k=j$UDVzp4uT*r1DA)StB9QBPJq9T)Gk#@f z)a@P88p^SA?*hyK_BlsP2$y40(>Cv`uXn8<4pNoM~#==Q5@HeJsA-_1hxS!sX zz_o+lZank10O=54q;ChOv$&Pm^f*YWYzIU-5LkCniZ9 zv!4W078jbn?w?wm+q&V;H4MM@cel%2R{SleBkWjd7QZop#!-ic6IQqnivBhVM`B~M zc`oEjM(W!pCgxh{&FJEi(!Wm>MrvC2i?Fg35EQ>=NT3MqrDZIRR#HTik~zz=zrmGe zi-2nz5bNee88!UaX*rGRuc+zC&RRFfyPS%k{L5c*F(H{jvIA{6CTwpg=~H~_9c<{Y zYgc0mpVg~_#X>C)OmjAO0hl=HQ-{dbm0X(`Q_A(k?FGuX;h$<28FD_jhM zr{KeabN>RU2I~4JOj%PDSF$llt*o%7!}w_R61;#*^}*vtutLD2=_Je-6ZTPHX(N{I zDG}5+BuY-Ux{>s3`NEAKWK<)&*!IGG5&976KkCNp++UWd+}(Jzp48uJs;;vNusW7{ zpQ~PasIR@%|CtQ(w7lL%mGfTf9A(CO?J@Mlm)z3{*0qq4V|_>5YwmQVx$Um1T=1zL z!>GUhCVnq%BHAZI$4QMbp=w`w;-_;>`b@_9Kt;4BHLOIm^f^$0S|QC!TJ`qW1+Bc8 zJBozgII!kYh@EAUWog+nojCRT_Zm-To4cZnA=K`w>-u^UOIC4%oiWETZQ_M?W(K&3 zt(55Di>hzMVZ2f39#m1xIxGPR;dV7Q*E1z(UWvZAY&pJ8xQ23X&B4QN=tcy^U7c^sH7rnL@M4Tf%2zm z451gz;7A)So|o0bkum!=J38JwosQ>ssmsTbvqx4SKBd^(n&H>fx1Q8saphCijd-x~ z{B*$(s9~=pQd|`!Eyj>YZG$g5FAgAPd&KlEd!SFf*Y=2inD8ASw9hnHUSM7SDsi7?f@ziP3IC$R(c(SVPykY2`QD_Zf&b=$vf~DU-_DA-;5zVxamToqi zt}VBsIhRs&u3AhhQry1Hm9C}F`MxJ=1i8PW?Q}O&+e+V#e(Y7{z+37IFG~53L$V0- zyoZr68!%lxzuX{XSHCYla4ZA#PoSSM8bU1~n?bPEcZdKka}a1PLbuS=CZ0jHcCb#7 zfKR%92N0TTQ?NnmJkR<8Xf4}b4KIi=WwbhphFFA+IULZQnXh^WdWL4Isa~)emfHpZ z7VK2d{(Y+wYF+Oq3caopM6#?~KD)s2l+C0PxT#pGnXI1{2tP6@oG(ns(?jZb0cy!2 zSSxHc(a@9f9fK3EXp|tMH1|3tt}gQyc4IJ;imof)IpTy>6$rtHV5_2HHdR}!ik7(4 zLc)qH?MVN%##mw>j(7gQa%AfJ)=FV#>el!?qO+1SpiuCSp$=)KTZzpG~|3&lsCOB&>ow*AEUY-lln4N0e*2JqYp5apKpMd zvS4#%E|zG*#JsWksg@#Xhs40jMzflkzvPdj(Cr6lx&IibBxWI?E@Wf(!Gh6hF@?E7 zYu4+*(?ZPqy95W!2)_w)^~gFbaN%;wBD@a2LET(S-n|eH^l6R%_s z4$(6z?~R4Y;IWKFzBS&Z*UmVW0L7OC3Dr397_v34*`6jS;nNi)&|W$$6kCMbQbjPg zB~b4(2BKO_>(_a*w~y}E<&bbE1hE!xvAbGC*B{D$_YIe)Xh^@kbfCI-s077P=D`hD zLLjwq_*S^`rwrkW5DJg6g=FSJgb-p3WKQnH2z>Hg4qJ>47qL+S6+B@B&34biCi$ZD zRD#35Dj^$|l8Ez+qnGzh#>VD4f)u*wWfu8K5KW1=;?2TAfi@n4`i-%FYrB~seLX?x zjxsm}Q5LQ2V&&q2r$`J58oZG5HwGXN;{$U+S^rL_g?=*C!d@P$JRuhBLkS{bei0Hg zt9P~Gi^64I1y5L!adYD*p<;m|-F-nl;tt|bbF}HF@6gMvZ&s)yE2jSmkc1jz#)29f zU;72dmu|D2p@!=afR->q3(r`yG;s*bech!v1D~j0#_I9Ee_;&kDy$x~HC1BwZL}hb zh0oz|yE)RZ+TV^q7B*Kd8afmZ#=04Rwhx;@mkKrY;u|OY==G;(2f}j z>|vC4H`4TmM6L^D5-vAV>GS#10e*4+g4E|~$xiYXWv^>1N%xNK9XYVFO5!4}wT8jA-Gn7eb73Eg zl4ohrzLVWuOP6qLJ~{JAJ@XlbqcdG6F7$$up)vkgU3CvYlkkS9hbHhMs+P*A}d@K@p9no?r?1IW**hV z(zUayXt~u=y@%7iqH5<|xcDM+55mFLK$7{Zb?eY^oglSEyzWlEpo>EpOJg?=$;W$B ziH$&!+lgymF^sPRw|iuN{o&)z$)^wjkMg}wiTb@Z)FKf4?P84>?ZLFSZ16St3V)S z0cox&y~9Ij`fRb}8T|HHO0$QlY=^zHlOU@JxduH^zN(-~vy{#SH6t-Po5a#mlKF(2 zSKI0N3l~eRCIDTVq;*5%TSMnJ*KsVJA-C0-QHGX9ty!Ty%E7U}~Y;yeP-$e;{oPch<_;+~rZ@{IAJBiaS=YX46FnL!5q zM)mJ`E3(W~hh@F%bJsiSQobt9IDI{O+D`@KPOUVig9u3?5;G@h%94t(Y*hxB%r6S# zj-I>khQ$#-YyIDSsjqx>ht)Q-e=Roci1a@Y(-x~`qIY6SXeG~~HN7F)*Y|cS%m-7U zv@f;8-c$F!sCV#hzWl6HM3$`5`g?3#_-h%V&euS{Yhcs&I%}ZIK+G6_bpILPR2J4H zXt}U1?mxHXdP1#Obj*-B@V{dDR2W+>$#U1dbQ&AEY_bZ+r@n5W;*>dk71`T>*ABL-jKN7!1J;&GC51!_B1D-j2x6Q zY(UB7@#@z7>;;(f^`g~?wddmd!oEel^I07=PwRKw&u~)ld1JKA00T_0 z)@3-t9(+9=c5l41y=TX_I{HmFVYG?n54Oagp}u&jt2$W<+XLGjUhu1>#7yuSsp5`&ps8p)ITngtE{QT*7Xw_~x zCF5&z2hQ*%kJGP>k=v!fto0dR;dk>+ zxO|VF*Gi6Os+Aa(9rD@gwf*_=@dADG%cSRow{G)_<5p%EYCDg1Y|>vAp>RJ%>7E)^xO( zL63km$U@80;p_ErYVn?0jD>gGS#RNJ=V;$tZFxho)nwvZ_co?)-D#+tGCscPNwKr& z^nP_K{O%ziFc=BeYRr`AeQoOF(%JZVvj?Y^k4QOgvsHY5$SPZn7bqib>>X9>LHVixI^VCd7x6ga$NXkv}eh*4kmDXHQDb1aexDW%c@sGv)TbI>)Oxk@!rEv&&bT!*elOOc6ByVclCa2taKZ)z18Jq5VIN`cW$wY zUWs7@*H(sbVkK5*eQ+hd#qe~?CGlF&`qd+lbpA{uPnz?tjPT(Fq%3vH)P8%t0`bg9 z?=A0zi<3iNUIUF;+RiX8K~w2h_L#Rs3gu(*qs2y-M-2Yt!=*V79MM(@f5zpKpWk=% zm{Twmc?`e;6&%8@eYX*@G4Lmkgy2g(^?8t!w@D=`+y-_nBO!G?Cl2JMx;kM#Kqb+E zQph9KfZV)5)t^HJUQWDCAP(6w)jg`PN2b?|QSYIIZ6&DCP8|HVDZm7yFN=1*6ike& zl`L^JR6eqkxB*uA-A3-ljxDd7)5HJHirYof6%r722MV(k-~KB2>$%RyWB8XS7l#eg z={J72Xx)s7PmJPMc@jD|>i$_D1^%$RtcSiO57eS)4J(+7jpCpGKy16!PeW6y2fEpA zi@)}dZyV`({dGx-s6kqRldmEQnQ}Tg3vfIz2Q0nhLeMwrxsn*{WT3TQMG>s#T zU!r^A0xOy62#$fno)8N7%NxtG^;-A#NE3wSU1oCj6ZAD+jG% z2l`HCj1Qz$7NN#~>N1{C@lb)dWJ0AHT{}_ae(s(Q96NIkjZtyX;)mw}(wL zPxu;3<0RjEv}7%0rsWa#E37PZe5%N9a{;&vGf{Ik#dQ7PF@k+EIkQpeiyfRsJVRc~ zu&K6!JxY1dGl$}Cg785ma28Y+bCVY|q2qq*qBUs_=}=zE6P^||Pc^2>I#==qxzT4> zqE?J&PUrRnTH_PT;9XbBQq&2*+HGsyJA&bnSqVH5OV#8)O@xIqL6wA%oiy!YnV4WP zk1^RLD$yo2^dvs-$Zcu7tiOf9j^0h#WER*$PKEOE#(xu6piYF9cHGLu*ZRy78Q~k` zI8jj|(>a;C)f2+Vz-QoTFepdsI5X9ZS-OufviYqz{=EFJ2|#@^rIFzC%LPlmY-deQ zxnwV51CXmZQUe60digS`HObk{OsmSx^78q0=&zx)FtqBF+s;NPwm4C7tU8+kXqily znM%EIZZ?=XrL?CwwYm@2JF%DwSn;YImLwZ3IsIv5zO;Q8Dp4bE51Hd&h1C6PbhiWM z0MsmMSii4|9Ub*k^RxM3r`!ANNyKn}mhV~=LQmdd$j0ETWHB!9^>n5{RWlU&3R`Sz z!-goJl74}3fbtkoh0(kgWEvJTU9_lv-|V3o6-y6E7TK1kZg43k0@he2YYp} zYvZO9F+^~0Tsti0`J8|p9U|d!&dHj@CLHK?$o@LLe%x^-;W7pL=)6nmi1W*Wnk_d3 znuWQF)|ZXHkIECvxRbhE{7xM{H$?J_a7plZgwC==^hl%1f_p;2UehcwPR^TkusvF6l*6EoxA zJ@iWy<^9RN?HtgDzXMyh$(v1R5|-$bH7({(!_PtK-yIBGH)&tgOStL)jNXQ~r~U4# z@9o|d<43ohp)KksfU&M7bGLNZp;IK?mlB?T7n{`3=(`2} z;%2~A2nJ6c%%x#2md#_uA+r5|+Q73oNx#Gu23R3@FLa-?h9$|v?QHBu{;c8o(%61H zvNnO%z+B)P!ruhir_UB|JswY>S1>mm(Bk!DKMfnNLhoE-#_S3W?aw9rY&Yr|M6)0%+9)sfBRNZ=y{Cyn@)@BVgQl(9E)iY@V7l#mYnml1aPDj1x1{@vv|_Bru&@_Gln3#1Eh z$CtsM?M@{&*B)p0y!ASTJQm|k6Tn`d+@jNA?wQCqan*7O>P(mn2P~knE3U%XasTU} zJ-uc5mn>cuf7`6PwiX5-*vPX&<&8d!DH@AanFuYdK6g_#|Ag2 ztG=o7Us)Wzu^M%43NK-GF#GCZ-Igy2dIX+}oP59zytw+|s-oRFl*&WHEYwtysmgjZ>z@GV_^^x%1~!hq6%?2Rd0;3O z0uLKzoXoD}5|tr>wicU5VSm&O1h^M*PwUx%;PWykO!RS8pvMgB`7-AeeYP0E7oryk z?Ji9_Wf+eUJC3M&-$?UBL-=>!?>}2{x(&ZJ{l7LG^^Zir_1{|Y|EcP?F>p3A*E6?s za<(1`f{vPn zWL2A#g^lC*R1I}_o_@19T-3f$S{B?236C29ru+o?}(`fQgjT43SbW^AhOdg$D5erQlV?ow`R zURRiKlfSTJ6G?fJO}}V7XLb>q`J;tiU8bCAKtL&vL@72@Op;T-1VMQF%!;sK*x26~ z;#5bPHvT}Tej6bqapPSfLNX8DVuX*Znt57x4r12`orC_@G?3h}$m`p6w6D3>gf$x6 zfF6JG*?fIDS4Urs1EoMzy%?HO6&VCYMZGw=Dsl%ISt@V9Ql+n}juzRFs!IVhY`cjYSbx`kyH|juS15nHQ*AL(|-AUNab>k@z5!D zfKEpx{zT!l7j>jaL}6!GukqBGEzfVv4y z{rSO;NVQZ#@kL$e<7qRIReQuup+r;}Sy5${j4M}<_NF*aM_7pkD7MRR!=~TVo9ok1 zYifn`)&&ns#nongpZg3Z+xk|4(3;D2`!Wz#^(q_`BOufjpfpq&`+W&5fyt#hDlLA5 zpj|Law|5-AoYmrY>e8i&4juiQux%Cx*M248t*LXM*+LcP*Vr4PpEw2AN^hdMO3g2! zJZ5O;XcxC^=;oUuCuA+5ykaryC3C!CXKO^$=}srsY*#12XA5bdlBFkGHplWzdZ;%i zIe1DOr5`%cR!8f$ajjn7kgunm%1f>J9R@i7zmF`wgTTaiXIcW3T z0eF}_SfD5=l-_UPC3H0`tr59CUNON}=n5+6X8c_tn za6yQJCQ8>zuGxd9M-}}Il?q|6EU#>6v0ke)W+!H$H6|W}pq3zjO?Kx@j!#T5=*>w% z8DwE$;Bn&MEn!DP-g@*o+EkPZz~z~1 zBT3f92;q;q&?ygcFyrW zvZKiAWqY4L3bWhtG3vZ+yqg^?zCrSQ4bNUdBKfVm!J};$~R^>k4_QV(KY69HoomoZKor8DVxQjPG3@`p30Wln_N!JqVnpt+0o%(EX6{nz{2qA zONNR1hWNEBIEuKaIXiV-z4+LFyqpg-Cv+;gxuITXn=Y_mJC01%$R?N>Ii8$D!PJ2w zngP}HXZOLkvP>ByR80|Qh!KZd!i3*=cEgUB#+UjsmnQR!&#fdqSg&4p8W^?q_)$VC z4#?$U=pY?rfB;A!OOWZ4cj-q9D-Nn&;RhT)-5&})w*lT0a65+H;vUu~#nlxJk?5jA z&@ZjfU<4_ly}4`7>%WwBfubuV9atzJ%6LRxW)vZEJ{YR$rQs=wKa%RxDoI^~&{fWf zkFrQ8fw>DH?1Ks_sYbgu#lO9oCv#5q;=qRO?&q43>l2JOtRS0y{E<$iS;QpPly>Or zhBp`Jts{=*hLVc$BAU<@rgquh)^ z%Yjt6jDrr4k;U*!F&K|)`nNz{oU>NP0spr%=QTK$_I-ZA2{DbWx&x8$1pwb zo&XU=&>N4x&ocNF$pShc3{81Q(?M|cpabLd#l%2;xU$QiWGLb606r7|c~FQUEqrgvI@rvZ7eB1#(g)NEKu_ z&rbXj)X3ECOkG1cyr^5g=bdRE<1DJbT6SHUd^dnQ0MZbBl}Jx)*z^(hUrmKLYC#&a zA&c9bY*OPMJEVI$)|>79DXW&UgWBd(;pTYOe_x%TOQJiGEL8e9Lv}vnsX$EGkiD=Wdqk4U@jeEUVy7M!;;@hK& z&G{|@{1&qo`owld{(W=&J$D_U3qhf2(>vmD7q@}dkwuHE#CAcS7vrGMOcs!HZ7rW!z z_%^4K2c#mlLOon1SZ^h){6B9jOw{qJ$Au~R>KIH~RSJDQ2FPIYZVi`9Ap%KcSVUYa+%$ohuT<_}{dvNDN&h{1`dW z(cP?!9QrHcMx;cOi2a!13UizzVFeeu#f)WR!OX?UDCHGdvh+Z?b+sy-&DA><d1VY4w0Y(!x{A`j9ay!a0|ikX-f2edR_jvOWOY(`uacI%Y2llZ8t^XcSxRv#ucy7C5VvC z8=|q81(B3wEb+l3UhvuZZ2Hh^5c4|Vqg+YmNm8%koU8ajyw3R3o?fkN#qrrdtbu2JL;@v^~&=W=& z#(i)=GiqPquC7$bM&;h7bB01^uqkZ=4?(0^NKkv2+-4L-d7eY@jHmt8xx!Tbgke#O zC9Vr!Bnf-OcQs_&i+2fTD*S=&BDw~y1L@x>!@29>5~s@FT7z(Ztmunls0W-`&@{cF znDb^qtLLv$L>;D8yR9$WiF>!!jW$$&lj=JFcGq-A0M?b8z0Whz-aGua|1fH*9fut$ z_~c_=wrLULDkW&LKhVH5DvP>@z=%%SWvJg>@vsetWAFW7{RzQweA)9L9)FUT z3pX)*R6d#LiCP0s+(?=SPT~XZjR<+-9mWL&dbv-YtNidq@oPrGRgg5*LQWl~OUAbC zA{y#|E!w)|99h@4syx3E)Pyf%q43TnLqlVdz95Ro(23V41y(i*=4e4BuLLH98}jv( zPpV8Dt3{moX$pQ*^KW~w`lk`kJxRcLXOqdx+vrwya@Tyjv(rF{H#pTz7v@`yh62de zb!raAZWI<;-Me%7w3GgTX3(SoRL;`BKrKHcy+ugIErqU`&V8!GijAEGJvuYO!RPi5N>2+NIi1} zlL<$qhog!G)^c|?!jJ-=DpkeW=J*+I^a5>(X3`Q9gCto=HwE>GU#9{}kw(g$qxk`b2Qo5nu4ZLRT*(CpD zB-WQ7DfSy!?Kqpr>qn|AJ%%Bt;4<;?5Ptb}+37E>%uK9Mw_~1OIzd$C;=uIyJHf{9 z#pQYHu8bYCnMt6U>6w#c@+;qe9!#Av=Nry{>C4&R|39M2|0`nqe>j)dDzjEtV#qn` z%aT+SRveCBoz1H?5=5;p9S%Cuuk5vT^Q3|yRCA3mYU!K zo8QL*mZM>Fc4xwgT0p!X07q_Pf~Yfj)VhOdmx`jAiTX)Qjw`JvgXDj!{#eaH`juBv z2;wy`&wb~?X%VUNo&Ay06sVuyeXnP@3XvHdXGmP<7>y39u2y7{SeZULt=#lD*f(Ul zVysAgdbSbOmLrj6G+URgz_64Vb%MSB3SPi=;o&2x;>VFgn2~rF#{W^Je-j_(h7aYE zA{i=*X@>jQua_xC)a1Cn_DXN%8V1Z}foj|v_xZG+*uR%(O=@}J@g#St-R>v3k|YU_ zNtw&R%bu5HkEOW-px}FO*);^|wqE7!3M)Hw%Nuh0!2jl?IFU#Y&~yRSSanMR+)UGW z`ie8+8aGUP;5Yn3XWZFx$hathh#5*D2MR|wmftZ$aOdsaTS|%;lTH#2j6Zl!2*!B4 zub6}5kTO|6@6w&l@UXPQjKRPvV`+WFh?-whug;t>BY6Ly?P_4_;7ee@VbS5@L6*Ip zen@kSUMadWGfSUX0-l+^Y0rAz!enNApSiL16m0nKR)C(GSB3 z-aBh|=nHhf`Y{;0bape;il+ip*xY9hG(N$i9s88BT)MI6^JU*t5xv@U4>~~q+hkV9 z__QOpa@Y#17^~R4Ua2oo`$E0!nL>G?$OFG0b9=b$rb&eDTxm`Fz*z73qf7+wt(698vo#4quBl^b5_qEz6W<`4=fsbnY<;LUZ9! zTe$rUX#bsNKtudfAzY2iBf4~_T`o=Q?mTI}ro`(Jl5C^qlLs%yBP40|8>`jCu z#V+P9_HxmxM5+cYk$BeX#OxZe%sL|-@V===WvzLqYfeQW_mhpt9@YD{@qR0;z|>VC zdQnLJ+Gwa86}ZmU#Xk`;?IrbY2SPI2{~4NatG~9tcNh&_*8&RVSZ)rLx9>jf$RZrr zY8SRbWHx1bAv--yT5lgc#v#AgU&kNOa)UO%Hi%^=H5J%BI!-TqWw5*XZf(bt#2dns zhx3it^M3Vgm`5{OcC}zJ*=g}Pr^oSa)~p+CLQ;oIFJ+>C<*?#wx#F1i!0VZP?B2D=`C!dQ(cLp&*su^lTtKDRF|q?t zKyJUvo?=v5nWf((J`sqxji_N+d3XAxN-$Kr*e-DV-T}|wd<^}NGo#A+)l9sTCWjNV z*7-FwBIRCYD=x@AFJK0q=(mnH^r>7FKdKPs$xU$Uo9;ggRDn8Na5)kHfG8aR0R4ZP z9R8oErvITvO>=7flZLsvy~UA-BR;2Gy)ne&XeE&-I#h9+d%4+Yym+q0Z)?u5Zd@x0 z%@niG*oUTLNTObX%I?9r?Zctb&*e#={(v{YCcpt3$?WChn98NGPo2dUd#IKh5&eP3=9uz9zkEf9~wn>*kgi^F!PvoA`yu z{Y6Xi6Qe~)9}AR}9B9DT=P3$J5xVko=Yaklv@Al_iD~HhyPRYuXtw=fEJ0W4#I zVgvOU@EAD&1VV^?AzSCuLrBO~dB`FeZMN3ORquXdS`@S4dKGE1IqT-xjHy(Iwn9Db ztf^kLc*QnS^OIWH3-3F2>YJuqDvSxtoJ6gJXh&WpBg@p&quPRp&CSIA`oh-LE@#}FWQ1=V*b-1K5;-6rG!9*HIyFtu(9wjXFY9C#jZ zqdxnP;&Cxk-6n%~=C*&p$Z4Sfod<1vq zS9qz#{69Z2vsQ@${l}e2OE2El7%}3Vl3w@4D3VX*ngwO7 z91o@j7KWPG1ENhyvrE*96g7&=yL2Vx-cACKtX7Bx#nbHxsY9?uPwqlMsXZ@c`~JWLnk9Bj+OPDY=xL9 z$O&vzl9ZDVDKPRtso9STiX*A0B0EIYRC}-vGIE~Pw}CkC7!($aLsNhjkOM9|z}az44!?Y8+gw1-K#G7L2(s<5 zVG&d`>*V2w0`pS?C)H7DlB<*4RR>+s5OoSM`^Y6z(iW=;vzX07g`|Y#4?Yn{fL5p% zyA;^!@*m!>+RBl)zd}T^ZiShw$@{nX)Ec;k8uK(wByp*@ei+RqM8*-SmWdM z7YzlLNh0%u2LZe4+>TB`QKS}7ij^tC6(wo`K%M|g_lgSss2GG=lR8YhNH{3R!GJEM zLXDMB!Y1iw1QdmJXpEmySPOCMDYkio8E4w9auoR5HZAO&RJR)paLz|Db z1$I|bcD3Jas0UyLJ_^ovwP%mY*9cTaR#NyzD0b9m%xsR-=an&k|BIb#Cwka|n&oDD z_5Q$CmqnZ4b-77z$Ez|{qGc!M*hP^f#&!Zp>Ml`V8U~a&LzpAT!q6`rEVVNskJ!qc znZ|Bjp;)%(I0M3}6ZQGPl+A>_NdA;zJ=G!E$EBLN9A#j4m-UH)%{sXq(m_*>oBZu< z!`dS>nmN<}m&D#u%ouk!!=5?|RgZf}v9%{6K!LnMMlLTD473)hS#w1XHDq%b=Vqt-}Ru7h(DxX3cY zFUxt)-GzQ5nkM^v8+tudzhR9Rk)lS`FnUz5v{=ZnB^^ z&uq#RfJX0#g9EZHUj-xoYo(n1GRGvGou5=FVgWOf#;fR$6KAX;z>xbQjP%SZw!|XV z0a~D5kf8C0=SCCr6Q1W~4_N##$NMwyNMPJ*OWZwdd=RVrH@fTeY$MqDZI+qYapeKe zObZzOREjgI^)!9)&rYBVEAg;!Y4AyLgoMvukE{4L%O{m&i z#$$pSSZLA6x639O+i%gZm8Lt?8uAM2BWp0%&$7K5GQ^HBZeobjNmLdI1z5R9A^>;& zi#ScFvNk8GhwAM;==g!J9+Rd_Qzr{o6;8$2|1bUO&|+zD-MV@A#Z5_83PS8RO8;#m zDiNoX4@b_2F{h*RCsrr(knHl1%ud+$#Wwot&G~4Kj413Tr^8L(=_ur53~FxcnuTu5 z%hu`0+~75XmVVgD%W;g-{!@a+sb;28qOF$8sY#j1ZF8wY>PHD zsQLZb&p?I%V?kl=2+SsHV2TN?n!P#b!n2_Md99UQ+3lQ)f5cCzq`h{u_p;R0AC`yP z?Jm_%lbWB&0-Z22)dS(nCX)S2_Qu!)6;m7_V`vUzZoxN)I`3>0!zT@cTORZfD}G7* z=(;p$452n9=>QTCqY9H!qn6=+Qm_!B+~w|>5j>_n7T~7T`ThNX^iR3mGMv6sU%@SD z=_Mqj=Aog{%jPx0U2Ei{UO_B`shcAoajyhL9gvV*ctmFh;W+Uz=7E8v2y z*mTt2%JV!%&LY;=V3^yH6O;C`(n^{nWoC`0?S%$YUGf}nL8WO``v=m||E`@?QJUkd z>wd}4keV@mFn_(O{0ORzY1l9Mw3Sq1n}u{@i?F#Qx$ zTX%q-D{LWeTo6cIKMhioYfooRSu@#Z9J=a|Bmr}}uOdmIB)whq=52$j-DpML(_(>S zh^J76la1tX4(hS#2^g*Bv=0okW994XuN?VSka5WgdSu!d+>q52-xX$eU`@W#|EQd= zPKy8?QJ4(E?Ehx%;0a|efbJDtMVw14ts0EB*JX)D#%*CnCcS_gHOBOU|HY}XwQHWS z+Y2u0n>ry6hudQRIT`paJ+bV(TmQ$9akzowA!+a4R?O#LEl=2h5%ZO z9oWzBp#w*jLA<%P8sY31!5adz<#}d1waw}Lvs;V%bn8y0B!%5)aj5$Tz*dGwdS(kJ z)DEO~x{m&?3QiXc1J}+q)N3@;0R4AaO^@Y}N9Ry0e4i~uPsL6dFDjyyb}(;gzf;OB z^=5j%6$2*P_}_176r=}FuAd*!Z<_XW`*ktNe!hF}lgIxdW;-;^fWK~sL^g2rx_=aY zes^o;mVHI?yTs+dh%*z$EppuM{wa}Z1u$_wh&l?Jq#}M1w(3EDfjZG4x|-dD3pYgY zkzc9kOH>x=ty!rf$AFWtso%~Ejz_R1;&6n)fwY{+qzzXxZcS#y$YDcv^v3KM}Fr&@jMuKoV~7 z#}}GLyHkGtp$E8A?q48qo8(rG?G1E}wi9X7MG5r9)zpNm#FMfVe%%4c?{&p`K67D6 zEuPvecaH1cDm@%lw&9)CT?A-G&M@i8Vep#6sjMv9gha}|4vw?^(+2O*J$diySZb&E z?^>qW%}-TI+NSi>GLlpC8n%-GV2s8R!hzDY;MJq~_fFMrA_xK87BnLP9-6mc&Z+>h zmUWQ~fn3$~OGCh)*5bS>gP7$77!6ddB3i}h z;SX!tk$m)5$}|Y|FaYGjBxY(AS376Gcg@u9-L~Z0nJarMX%qu&yZfVP3B)JcLJt)B z+80i4v)W#wn$wSC_J&Q*&)Qp@ck;*kgT%>JyQP+f=ow{LXjcWA5|E==v{{J10a2*A&|bwviu&AU!| zFdU7;NBamKN_v)O-ywHI4S!$SU_1J@d6k8JpE(wi-I2>3{v&N12X|caAGR!Ju`74> z9lhIirOR|r`nKAZ-{I;0;g3B-nw~fP*`dvilN@CqxrmSst#ATpb3>%-h&~k1b{X^< zAG(3P{z_LTf;dEgqbMU9b;hU)MT~biT*aJ?e+b{ZTsmTNM0zp+jA{#4DHJO>7r{Ak zoT80clEu_7l7%J+7Ud?}pz8<^x%!3x6*)d{`oxsfTK9zsMo^umaMJOuH96=`ZEN@< zy}u)QiVMGr8;4@E&aKZu324T5j6Dv({>BC0D>=3x8fCDqL0RkVB~2Ii{Xhe~I#%SZI|u>ZM7_w4TpTq;4{4KyXHAO5;w&(;fTt#n+@k>l9W zG|Vr(S%h3Kj* zEW}UEVjmN=r_-QTn`^?`-l4kgoKX%=8(&y-O4IbTe8T|gwjy(zg%o?^af#ljc2cUf z06r^5w6-Xips4MsB~Mp7nCNX@-N;BdFGRN3RnH-=(jA}spDjTfKHjzT@?p1M0tWZX zvFk&(e+d)K>1pL;rk3^Tt`1I*485ouRhcJ#joz|8BJ`?XJu{bdpSzl(KTEd|3ErNseGrv$^X0vTO zte^JHelvbHAhz{MN+^wgd!_yTVW!kgkDG4|msGKjk9An!DC z0V6RQqT{0vZ*;QRu8Om|SLhrjMuRE3zKh)UPUI}_!>fPxH@9EOU{^ox4eXkKfcjTR zGt^W5=4@bk9k@2H{2m46&ui88G~UX*Ur)NldV8KNk%h_H;BxBlIeR+IT1c6|siH1>K+gw3gne=i+|NlY#j)dEEUAG#;PM zCSwabxfFVAe)}5Cua^s3$b~Mx72&x)>c^|We6t!8HDjf^ ze78!V?vfrZR2VkwAeS9C%L{5#7cU@I6Y$h<;aH5D=zU{;%5D{}JI zh>>nJ^0&?Ja=f(lr6@y3XTCJkIMY!1uccsOQ!+yWdZ+7&fpZ=PV!^3Kr4lb>Vod*K zZygt>I9L=1I*tWmY2P29RlganUu}IL&%#;OaHTe&Is-ZG4>N%F>aAsz?J)L_$a!`_ zaMVt5F+HZFza<$%6!5;g&%JxqNL7ArPwI*6A)9#S$H8WR%z=FSG|K$F3VWOxE{E(7 zRX|H(ml9!gLn$1`U%U-bFg}Jux_ky>veT_lWzw>a1b;)}bH*YlKM6EvE$M?IS%}MX#8j zw(;?rck6_oR{f_0=@lTkVf!_n?$Ag4Yuza8Dp0)BF?Dg~2`>jLX{r0XmTr`r?tEC7G(Nwkf4#Bt*wcXvxS}Q|HAb2QP=s$^hDU$V9FXR*}Tk2tO-OJy0auQ7G`~B6^8QA&F}?Bqq0Ni?#&*J73_B ztGQbJsfY~nFoy;PF;^N0tnSa==kL!7=RpJy4F9yW&hxV&7+c!jy}6#goxW~o`aH_t z7q++YJ!-eIdnp zq*Fn4sOwnU20>>VV>mc*~ z&Da}NK<6u?SCDm_$}Gc1!c=WFaSxvr$Tz-oo}@L=g09r?h{-A#v%RGh`+fq%5n;ep7%+fmvfj6p75p zNNA3dNf#)0g)R95c_Kq{^4}zb!IC>=#bjtBOBOL@mhP7abE!4+Q%=?T9#@!*7HIX# zG*(0xTag&YV9^wT)d0A0^^_tz@e)Q?AwnYxon#clsBTXu>T;Q_F8Z8wbie20@Zc(& zxM`?Otn_OK&uS~9`+93mubHH=4#ONQISaP26jol)N6bmvbgT|m)!bwC8R<$R8>MMm zjqd96zEt6DXkuuj0tS=05!i}%`Lny$la3R5@+U z2s&*|?HVRLQ0o(g&v@hw!-3_$;ePk7(p_+~f%5hzAw14YBu?3XL z3ilYZX=1?cUd!H9?Y$7j9I+{+&XR@+u8$~;JvFgQ4M?#Wzio{4VR}UxF5O~g>KhJO zd&6xxYw~1fpqt)m+p(&<;@nQQOV(Cam!1HsUdt@{HJqPoLF9HGd8;b+Xx<=y0@cC$ zID>ST2sW3KuBSgAe!%J`#HN!`15JyDGvQ8f;QF9q-&QOiv(^_6#vJsY_jETkH$|P~ zv?ynpZ12Twe=u(Lboaz(`WCa6GT-$RP64?3(1K`m?}o<($?*!l@t*vL<`fXQx@YsL zlnu|!@;m)nax=9Dr94)_c>ltz+T0*7ZanSWr!cQ?bM$U%FA?d0A+5}VWOCxx+*TNX zYO~5RIybV3B6*{K^tE9{q^LF(1uAh&j{v;9YdDyZc<*Cxmb<$=UxnJH5lM@xl9@WY zuzdVJ8*~BRrLC-Juw@_GenC41+?KZaw=}xq-`Cg;MknrAM)l z6eEF1m6RLvv#J!b-C;Y^71forQUJQMIj&b_s2(9uCXTDCJ+nv*HzXuDR;({UHtGXj z7YhigZw@FesVh~wjQxqLhO3CxFnQpYh&h2Q@ohtTyrP&>Nn=lBlFra1w$@UUGuZ5E zu}G&@*ZJHqaImtVZ#C{C*J7-ZvP~l-(y>$TgxO86vw%^0SDsI2LMK+@amUV#sKB%y zDmJMAG@aeKh-?JIbG)^Ayw%ZZ3T~>B2-`}(KAnxySJ6~bmbU2?0mpROF0LV6FB3^EjB3`a%^bAgF(_%fsaruN=@*ke5uwcyuvMO1GD_=bYMyTaQ~8h{aHGhkCZ$YULgMr;%3_OoR3cn z7D^eApWU9ZXWuiZ!gDbC!}YV>-e8t@fI(jWxfkA>8{rXVRVo+Q#*Dlf2~>aUXkUYv z^?7PN-l*&(9}^T5WT!*Er3L|Wo$X7`E&a`-lAc8uhDwiy1rv}(TR-i^{nq0)Hf8YNc3Bfr>>tQ_80kj4 zb4wc;gyK*~4144Diegp~|8t=A1OA!pLZgN?_Tm&^8HDDEamfAlz_7}- z9gF*#o0jA3?Q8mRyngtFHtNGr`xwbIY8dz^)emt^C*)lP<+DyQ#NU-iqL%l<<>l&r|5zV8u-#4+a=g}A))PkQo1qZBaQJ!y+ecLnNq9YF5 zU1q~8T|hBP=QX9URST@OleH4b7PD2;dcMgO(-s#Ut7PLbYbwz2wf<#Fxjxt{_q*0N zP<|&OjD{e8!=hD+J0N1)iz80s9MGza2^q*j*AH`nS@4}Ld4M1rZ1?W^nD(LL;p5^p zXq1i6N@fQ}2T zP(bqab?A{=Iq&DkLjw!3WHfe4R9Vy`@`uI-ozQG!oY#F*Cdde$H$Tp1H{o2u*$xj9 zYJSC5WoH#1ovu5kIRr%1fB@xOF}l;@pLCC*Qz-1|ba!H2A=Lc$Ypfk8K{#L2;=UDA zCl6cmBF?xGrAtg8xzxz+G+)vT*Lfi6q|8$EF;D*5rwWd_v_}t5z*k$o$7N1M^(Oon zfqxD!H8d+fr(uughdBgSQDQr+TRm8biQ;E=(wewkz+@8|n+CBu)0Nj|Na^~ytobcH zywFDcovryNGtI{c9+QF?y@Si-Ucd9R*DcG;%zmic_6D_Bj@(?Ag-cr_11|ACeEkZr zIrPG_=w=l8wc8r&takj&*mO`GM21KZ)IPx?9*5t9$nuTg5qD}(|NI3Qk=WWK6mp{UHKaEYwz67s) z2B+LBdb|ZraVY=wmsRC|xp4 zrivFi?kSVR<1h8J#A?U& zGgkG#-eV~U0RX~9005}}+nny=X#Kwo=*3<>@>*gor`;c*J^{f1>2P{I&;$73`i7IB zQGc+ou{OQgA?j(Mwj<+dh#hZHSR0rX8a!AUQn&ZGd;fVoaAmA}co-)vIbGi@b+nws zcByIIPV;G1N?A-$E-Q*GtNx7fX+300Cr|oWzc}mp5FwhWrD(mrobzt`bj+Ubp7VZX zt7^&inLWbgZt2G$4gC^6@S+^Ry`TEUBMgZF%ghyJsuQ4zBl1XXC4fNuB-4spP|!F{ zsvbc(=8ao~Xw?^J%MUa<3Xl+>k54y1D``G+NWkkQq`!td5PE7L=;#R3g|W$;z~nTi zoAoB(^QQ9aNVL?%sM4KTObdckN2?-|@JfRe{z|GTPXe@qYW|8h=ywUsq4W}h3D=rEr3nT3$ot`7?EsM!5mhQFY z_VC9J-U9f<^TPCsnU7N&v_IicS`#E%hK(K`<8)14?w9l?{{3e-q)Rr_ai-5<8feme zeuA3Mw!d4@v*sh%G^Y>M&6QBH1DYqduE9K#}jdd%Yz(-&|85wkd}%e%80w5^I~Th3Wgqa2UCTP8xR_&63wA%FqOvu< zS{2+laV%?tUiOc6c7Fk!KH!jw zOdNEaw=O|%AU>`*Yg6M8n)&hMb(URxP%=UN3+Xr)_5wkls8twSN&<;6$j1CkBeC}%fi<)9Tx6EDDTyN8&7P?f&emrb+? zw(sV@%bIy_%9MsyG+Zk@aKnjcCW!fzvg^hySJad;=rf+&`o%CgmR$KmqI9;A+*+e$ z-S`)FOR;Tn*QvVDR5u$W8Qa{OU&oWb#pl)6=WRa}%?RAC$Dw(dwtZ!vH_T$qgHx0P zsN26)2QZ}|r+_(F`F-j4c585{GE)P-*;S1L-W3PWgMWa6?LFYioj-;xyr7vculX+b z?>BK3Scn?FIS>GTJ3ckx!|yyv2Zt0@eA9{dbwE`7B6E{XET9UX54CnZsc!hB>dm<7 zzSm&R7sJ$O8)+}LLuFrF<)9hXiXS~IY4Ky#=I`z#`CAmH8#pNH#_5qal_0gddW5lx zdWa!c*Q8^hgIn2ndwb;4*AzP?^DI5n@g5m?lK%m&#q|EmzC>E8_4uj%Mon#jb|0MN zT}Lv<@o_4-PkKJvDMRA`0B45Aiv!oO-w2nE%kaL-yKsq|1&*|t}#5gb`KzBX_IUhK}HwI3q zH2ZO;O#oR`b;O`UJ@=ayXxOdm?&&&l_EXJZyyEQ}eVfhel!l_E^OH4_vc8tQA{1h4 zGILRJa0#k7qRA_l(#?K*7tYWFWRFFeL zAOEaCpL@^jb~>*0%xkdJWdK+rX<8$7OXKFq)K=WQHOp7iU7A9)WkMV}ZRqQKaJrTS z4D7x6NsO@%bq?eLkyAt!f>rWI>j&PL#mLfGn}Y2ias%a%g@l(!JfxHC%#hVg=a2b?qPzN{2ko0Y;wzV z^6J&72Jdm?p0*;psnJUBuF8yvkOl{zRdhQ#~TNSp069TRPSs`8F(ZHvXr;=K_ zl=)cZGHcq}tPd9-kJ=^08uD34>Qn%)<4LM+9Qj`hvW#iuRG&03u;ZR3mb)V^*X1v$ zKQL+1N_2tN+Q1KXcFRO6WdSIc5O`uks!~)GvY{ErOyzxPFd?n4`UXefBAoW~DJ}DG zp{fv5NW)(aK+SdC*&Riz3AzrL!R78&+eBed-$koL_R>cmid1etmlm=B# zl^#Nl7e**57Ue4ZuCklceOc3bMmqd6Chr>V%&jX;DRZukeW0U@H}fA103kXNQM}SF z5c)D2p4gs|U#*P<>hbBW!UWm&hB7>a>c*r9t1G&r(;Fds<`&${`j7%|yl|-6v z?C!qcdK^b8-8;U+464+WHET(azv+3DNJBm9e0=O{)`Ts_pqmGN8y3|@ znd!zHz;IfW&T&#IrLe8A?*S08`9KXEWKc1(F5!kPtIJaxgaZ!5kuIx$SSv>vZ?=&5 zboya^QwFX1?2sHS_x!}5|3M2`op5onU+rH}=8q}OB;4yZKXnIAj;7nZV9cg8H75YW zdfp=C4s%G$k@>(7&geu%X8o~4l?t63(_%M-J#I z6JGRmH##b_i~&Qv6N3wmB-TIX?s`Z}u%Pa*I+MQFzf zxU6RKGLd+ZGRVG;E^rx*Nb_0Q(Sqyb<})*rjwTLwNLMVi$h?Q_DK*@_GIDn*wE`i|u z4c!>=|Da6Z$>KBw1N?}e0O`Kv9_d=i1ab=eOe~}qa3?*_N(Xw96$+%eq>4k5MacPc z#YBkh?7~I~rV3-8zp(VG!RHd~^#Ar$IL7F7+LX6a2H6m4v;wdouU zNxn~{uiwVDGyVq$Wiu%!E2FV*pZAjWNMUP(6Snu+Ud&Ly=QmBBvc2=t5G)l=gK?+d{_XuLi&q|-YO`H_b%u`zXmZ4zh5i3!rlU-R*#H| z?lEKo?mB+qREKKX&b5IOhuk}n^|hf6ZT@_>D2GfmHWCZl?}AE$;%(1J`QmML2^$FA1o_9zgFN zHa2S+FKnKP?`JEatp(EXT=3kom7J}WoWU10ZqBM=OfWv}vAP#L8A}d4DbG80 zcQ66+z9QAF`?w8bH(qE0vSK*;tRg9dVjnyh7%h>J*Zk^wv-jDtHBW^Zo-OY7qrJhma~f~Ki4TgK^2rG7aI=Ti4qzpyGU^m z3=~W#i;8-?l<-{Dc`cM>X45LMa{7^~YXXXpj;AS?x81#uJo%uPYFivF*0_=56|NSU zJSw~jqedmR%^}mOKO-3RdFe<^*7nw#5oC$IG7;u!Tg$5=6ojb-$#}D(Q}*7HYAuFU zJ4blfgQX}LA^>p4C1-sreYP?}vP4YEnOooIcTPdS%iFRyKG^>t4pq@O*a5dSC4V)D z%{B}$`LdKDli$lDHdN&2D{7-PZN292lm`!6@fu^-+lF737&@#mP^b*;C)X4wms*T3 zVx2f0ecys%;RKtnHmOrB+(d0H!h*Z)hYk`Ejm#t z8e1AYmsN*)I~WYeLKyj`=pG)|OQc*26C*-$A8&{#NRj%CZgD##%1@OSE>8dy z*B@688oB2`gZ6$T|j zIco?wPNWz^on13#?WTzp4gu6u7^YuiK1_h3JFMj4DW5vR2Q)@uGH;2L>BM<=1{ibq zEW!3fJCitLXcfe7-d@Jyz1w{i)wtI}0I6EcpYva5JDp zkB^N{>cokR>g21B!~Uig=E#Z#Wki5G6uTz_0%v8?LGF$9&IN5D2@3SS5AU+XMJ!@1Jw5goED!rE8E-**GmVB{3m2p^=T+93>GoHBl!jFM_hi=Ie@! z*0U`P5=N9inNn4NG@EViU?FQE9d&IHC*QD4@&WL`XSdRa;?rIMa3*A*GL%b|tH?!Y zb`TWMekWBlmD(mR<8-ai6!z<4##eeBsq`E)Mf|ux)?dK03Fg#q!y_+q*guD&n1A!{%-7dk+U9E1TzqR9Y0x%Y zw?MM+IArc77qDpH@%hGje~AxYiw_rSG_EwSbgz7G4JDiib71{-F0KE)Pv?b|hK@!% zh(=?X#UBZr4?o)@d|e#bxgOEUuczHvL#yP3-W{zKdL!&!qB0Y!1oJ2oX}g7FEQP35VXtTIWsuHGIeW z{N?}MeY&Gy(uoq(a36DcW&=uYA3;~O;=XCFBMweVL0 z-+T$i3w8_k81L2=!IwNG2`nnD58ivUa-md{NZ3nm&oYpdK)MyTL2@m!MQj~skn8xS z@-g4kSwpHDSg17$&W~&qg0|~UZ@!o#RFsd^BL(Ozf0OF)oz75`ljb9;wsA{(O1}DAAZDZ}CkAlOuR77-Fw-!)D@{#^}^%C_3NHJ2k zrdg+NBB2oJqh!4JZ@5G=leY_LW+njLzdxRqI}8n|JTEEIzWw}+@Y^kt{*g#*rv~_Tk zSf0+g{bpCiyfxX#edtmbRuAzu;c^itlb{&LX`0KPv->ZR;bC4M9wGx{GVnQseDPtb z6(DAORf)Op1Uk?U)x!v!6lHU4ky+=5GIXS5`f2@;9=!(Gy%#^4NHxK zd$k_r2&EY}&qd-8+jqo^5AUr}+hz#N%uSa7&m>DcW>0bB9nwfq+Iu9rPs6}jjGB>- z*rWo8lzc>Dw2iPuW&ea&?nh}XJrUPFITOMr3spOLQCNFf1eYU7|AvoqM*l8iO7jzmJZkDcV(&J#8S-OW?u4Ide&bB zz7)LdtPLI+wB{sPGxpUbO$#|F$)9r)C=VyMbfwrRm|9gGI!vtN%CCrN5<14iLshcY z*A}pj8J{BX;!mXor3g&HR7wA(MsdZJNrfMYxHktFPXD(01rlxAPn^MLdVBniN?ix8 z8534UB18g0)Honif%ogwU8p6in!I#KVkRh)!gK5rlD^9-ssiW2S1xaGnxAnIj*#gI zH12=!vq&x?=0~v|OWqMhJ^qDn;4=}$SpreAlI9!Fa8tzPMag|7WfKMyMwy#yc%nxT zqbs{7Gjs1)4a+^zrnH?oEGB2j)zCng;~;h=YW?SvoR@rnqX0=^OQ63SS~7%0@)j$p(5w}5Nr6agWIDr#IP5#VsAf(B}m z{`oi*Hn}(-DB@DYeYS2n5BSxyF{v~Cuoxf)6yV4_;mzl-KaH2?6~XyzyeJ`! zx)3tuZK+xyGMrEZ$&1A~5;511KBAb;J2DTFhet*YNq}*8}v0g4gCM z+HKN+M;MLSLR`hYqD3T;9* z5Ne(9=$hi6p27e#58msYJT#uvw0OV#vcGeCmRr;AzIpfX?wNr0bA6@v-Vpsn)tR0s z39fDG^tCdA;tZF6(80i2va1TbmD`DF?Y17!cqy)$45 zkJk<#l$wgnW{I-u8Qecik{#i3GK+7M!S^f`#G>s|dqhd}H9mX*G!yVg4q|U`wiU6s z<{cVp8%R-_%clGd-;A$vbP%tY>)Z~$X+?*_!IKZpbxN~z@lby%#n1^_HG}Ngkz*ZRmWJp{N8%Vfx$4f;H?L?>=BZn> z{iGHAwLiluoa05f&2`)GwdwrLn&ZW}%|%t})*m)eH^R5rP$0IzDy&A(u6kKm$?v`W z&|XDbsjc$muqR(}C$HL8RxMC`UJ2lP(RNmjAfvpLW^(4S=4jY89>urI%!vYU|l4$=p?rECuMA>FD7vz!^ zWj4H#Ve zj+-mJj6vAKP&xZZD8|5n4ua$!tpA?8$tl~T3Kkec2_GP$L6^f zTGN~Cgok1tEghXIWl}h0njs8mKE>+{KK9B5)qP(aa*_zepiQA6tNqYO<(`N3!;8Ua zc-T?V>N3eJ)43hv1#{vBuEz7yqPS92cyR)%(Yteqt_QpW84O z7xXdO;@5L&-dECS#veRhy(d)1^19SSWRzq^dTIt0hkci{PB5eKC~Owh|Cb9yYtc6_ zhvKU7vGnQ?>9k~~bT*@xjPCF%b&aM_k;VgVKc(mb2(@aEdS?8V!7)L}eHDG>+Ih|( zsc-vg#80*&jxega(hWL`KjX%$dC>=k;FnCwHfGX&h7{VIj zcas=y6si}pfJNm^x*WpMHj1vlb_ZK$s||w@4>g&yNXE=^fDsG2e#0-}e4dQh+tpYu+tQc1Fh6d#gY5lmnc_FIlMNC{H&4o;{P42fs&YeleovBE2saf~~cfDYy8d_E)mx>1?)-@U`RHPRJ2`(Gqg5=OS5&tu& z234HPAPNNJalaZX;5{r4E9w$V(BhAOKpd99so*IRgS8n2Q*s=JAesQ0f7`lc*C>ZGAUSbL1xm33ENf)(~<;{p+qdg@kwOUV%XYALK07AWYI(HQ}tU^^$|vr-gC;! z32Td6V>^HgIb@bIH9(7(@&Z^p^&~m?I{1+=0pI!{SbY#Ie64fgP_PjAe_#^cSCL}t z{e6xsXX>B#5@#xgz?NTr9Kq> zhXJ-a-CP;cEHtbnm}yx`1tRm6+~~6`)UCvYQ3=aehE>WA_q)eils*kQqY=1T3pSpH zAfApSU5Yr+IK^duj8rQ;>od2&6S&K{BR#F6vJtRbUo5PHc4GCYd+W^Khj}<1AjGirY&Z99r&n2bt-e0EJ8H$W3S{4RwcpmegS!`u9eZl;4NOezB!OA-?42CTO|A> zOBoqRNg2$|aUXcnJE^y{C5b@Cn5e_H8tf7^d%ElDX}n?rhq#hg9@CvP)Kbt8$^RxM zn+nkrs$`rjxV9Mu^b*;10j#gKd9H$}Z^=p4MqscvW{EFU;WfN272GkK>0e)D;8zbi zfh2ryiI83(fW}LTu~DBo|7{0aHE|}Od#y~DLd*%8wNJ1!*Wa&L8E(Mv#>V`}GW_EW zVlqF%82Q2q5Dfxu%NyR;&k5e{IFKE=)xJMdnCdZ}4exhW%cP6clZZm}t{{-WcrfDF zOB899lRaA$9KlIEW{J^rFcvX>x}WYj`A^TUMCk(A)^YkHq_LtYR)?TozCZxwB@h%m zoS1Ih!2C#WI*j#CK#=q&R`u)NjbhL=(G4$Zv^ViM!9tu2m-X6|>xcdkOY8n|>8&HJ zZE$?x4MzrBcbrH+w&?!wbtH9h^adwg3@4L~$DL}Xbakjx)WNcR3&cld@eum(GW=PP zfwTtdAwN9%3CEt0n?6Ts3tM;BZ@73=-#RW6dv+rHs4WbOr={m_%U^oE1$f_u%D637 zZxqueF&|=6CHt10UWeZa*IKff9D?`cEbbMkZ{Y_@bJhdDl}=;FL6sf7oh@PTxgbXr zy5G_kObf?y;+PJ^R*i!y%KN(-VOsoPSz##J+r^#$XocRrW8k~zd%*rdaMgh?=AS;# z8ev}FY~HsVM|yk^@2J>)v^Unb`&TVIe!Skf)cUod-f>yJYLg#oAHlw1YDOx z@y)cpiMl7X0_JG5kF|p4to)XJ(gGb@p8!SRMXc`9e)?@p zo{D%=-KT8N*3JP4#R3)sMiO0@nfn2A|G}F~k z2Xz~Z2SZOQR-+1jgutz-XqZU?S^)US!hIvCyw=OCAd7nlnWs;08_+szNT> zf8fid*Yx!!0UoAs_OKywOJM05t8gCMKGtr#zI+F^M3~F63+Onx5F(j(3WA2Vr3P@f zxAa!o#U+YV*NcnQX~-cZC4YTQ#K06yMT5eHV^hD;X4`4hfOw)TY#%L~_Vwu_Db2pI{cv{R{f?3D9>63j4taDeVP6HSoy=P4}G<__1cD z6w?D2t%70$x&R5!EFkVg3P5S}Ss@`RaE#Hxo%R>Iy95um2LSaA42j2jh|=D;2e@<& z5GSm`)CPUgIfiyZ@8ZC#h;}id* zk00F=Guk?g)e}1q@v%Dwv1_WQRPVEzl7nHNMn?BV)62*TyCx$-}z%Ndd$sXYUDL zHZKC02UiU`;~D2Cr{SlyDUTy&84~v+Gz!8(j)?qx01LA}b05}XCFMgG8CGMtya3e+ zaKBsd3`h+f+oz1ufXt%m-wlA zx}l15nn1`T*6_p=-eV?##N!FfP=08Bk^sona<`F60!By%hILZ@*+58qSsDZ#3v0pY z>*uUAv_qw|y8nDLr-I)s8KF~awAyz3cGv{r|GxMIi^Mtn5u*k1q864-XR8*{hgTx^ zvnuvubxhOfOO(;$839B0o;USV2t$G_U?)UIiTEX$U>L~P?S{D5MVLIQU8(V@1cY+ zpB_n+`Vv3{Sl8n|2-%snqX6EcqXY>^65rtuD^}<$18`eA1%u+~W=?k@fyS1(4F!j_ z2DXz_9xtDvA_mpRaka3R@B{bRX_oOx#L*9pts{)Xc78;Hu)G zEP=$UD>HsZB`;Q^7$mvf9=z5;>9P2z;26>Y$6*)`?_+FZ!VD3jTcJjjHnN`TbZR%D z=UsZGc0q2u2W;R>sj+>t|{=-sSu1U5g{Yg^<>!y)PX)0e(HD5SKuEfy< zgK`Dd2||b>%z&War2;~R?m3Kbh;awI#}EvI4C5HF9sb0irh?y_G9q@$W|(YhaEcMn z`nSxSEqt1a)>IhXDhgL)KBRA^j#(;t?e~M;?^(>Xd_D!p>E0=2ESPOS^q$$_#`-Ef zq5!#6%-Lha$t^ZtB2INkF85_%EZRx38Q*Fpc} z1M!BZOqdoqd1*-1lw7@RoG4L$|L9FmI6K#f14j*kk-Rb`>}r&c8F+EQ3rMyau$euw zU$B0$E|33lP+|PqB}Dv-Q|bpWA+7L$CwF{H1|qrSoN5e{KAuoCyH5UcNpU*z3`zY>cjMf8Sa@malo~|#q3Yaj zx3J>cfOW;O;6~@(!V=X}%+LTuNr3rc@UeSC>FL``G+`J^V|HJI!Db1-q=40|1u7{b z@(e85FFagTlyGIH`!8S%#4pwXTO}ND~!|dVADR^s^6MnRqhkDrTM4BRK zdXw6D!;zdJ`RwB31nN z-Mj8tC5%sbUh$-xoPj5JnjYW#Pe=HZ3byHEIR$$O0f%CjtkkN&_?^-o z7l=aL@?e146G6T38WE@ttwxljUlgn#c(8BxWdxE9MLV85N}O&xNDAw0yL<9^ZaYY_ z>ut3bEVJziRAA4&)O1_l`>?ckHVSEu=`i+z zGnIQ_(jQRigg?+u6^=#0o$PqTY;E7*ZX`I(f2+AI&1)H`5qjgv;?D--tK%zxZ(KJy z@N{-Mw4C+xGyM~4s%t919d$@HzVAj>qATDXeZVYw8lEMX{o3&tH~2ry2DBI(TYI}q z3))NzFbBNS&T+_YxfQlNi#7t@AsR!eU!zbwM=Gk*Yz;eJQaD2+vj>-5rbN?w`W&)Pr<+{k)nD{%;2F6zc<3~_=rp$@-*pIgL)}-A8V9 zhDx^KkO4ZvzmYndze^NfFs)s+qypjZP&9YUVUZjBV}Ozlr=)X$rO#1W^IcfNYLdRZ3y_eF32z|{YxdRcT`K~I;xu6AP0 zi(A6dRsW}PCC2&|yc92orX}X;4zw2WdP8G}=k85`-lGbcTO|gU5+uhRnD?&xmQp0< z`{AY*i1_xn;niW@QytP7t$Q1!n~I^oR)4DX(kK8!)M#<$zU8$FT|WUxWvlK$Ksuc` zv;)PqSNfqK+f~bSsv$^JdO0Vs{RAM>;m&K(CWX4|k|n)zEwgo!Bh*xCp+v=%dBecA zo#aZM#oniVxk)E<%SOFAzjsCNQ?(kcZaq6Q6BK5eggu};Y*yXC7SWee4 z12G;}c0N#UFw%NQ2)hA=6`?RvPMujS+q8kM$t}HKkI5dsUCHW|$g0L7ZzoZl#yRm+ z<}`#nrv}PFi&1V4)JQ>SIfH&k54rj^J^TD&ZZa2_8ZNticJ5E9NkeEFe%$aNo5>$D zaez;-X)c6%P-`)KY4N>s2!e)i2|Nz~@|=MvU~3Lhig*$S$zP{A)y-JTPs@guU9fb2@7aKT)BPHQSB$ss)EQm)?{72nkLrgf z&EmVwH%NnOE3G*+D^2cX^L_~;W;BL-9S)k7de{YGp%lS;aoj3Wf!8gNpE}xpFS1oe4 zn!Algxy{~=p6!RTub9RunXd;q^V#)>oW|mpbB9kSa;F+>4%Mfe!U00P26_JI)L~;B zLX1Ri_m-F)*llpyUi z8B$mAD&Gvtj03NRu^EDKIV!05CkE%bkoO{%dd+1SO^lA%byJ`$)EJXHF^tv%C;rFO zRXQ)~)q~J(OV@i0JRENE=DbY-N<0QYr`6egYcyMZ=~r;_W$);P&p#SO|Web@Wick6G&ia^aEKk;m4|hL5mqFOGaYw! zuOtUQ6NB%KWz6<>FiqX}&jSt7Jam z@mzetD}*vRrhsd>Kl2&`9 zTg;FEx0o0pE~^5B8GgCD7byP&17JAfwMxEy7c#d-3@$ZD5+AaalP;-PF!=78M=LTm z6*zcgKR*hOb+A=;Ofj(0F(shEv49X^!#O4b{v0%)qG@`bJDNket(z5R&-|<3;@7ieGL}5&Kg3q5Na6odx~`V7Rsh!#uM`bD(4nG?bj6)JneU91;E0klpZpP(#Ko>_`w4Bu%+_rXllze?R8WBX*Z9LS#DliNNZ39p`$bc`LXKs|sYV?Cmlayvfp99EdiBgl1M8GOj3 ze>}zEoikUDD2;^X>0--1sr6tir=+#$gtaIXqeL03RA~tuG4{5@tw*42H@9pTH}UOG zgR8Clr~LRn0CB^?;;yq<75%+b^$18xW4TH6$Pr+@!5MT*hRY3FGjLPmv4h4E?MKB8 z6!_fb5b@9v7^Sf|3@K@5AdR~mtKBJm0t-KZ!DT_V7_ySU3U<9kQwMIV!Ikn9xdWRw zlLN&x27P^on4@@wab%gPw-kF=ScPA(XVWl}!d93{v9!ZB1-!Xj$^bPEspf|!EmD*X zHE#w5=u0Z_=ls!jsnEt`eEz_@X@1Rh|L=}#5po7qC`shbxJP_puBVjuG;X>qhsqT! z{_m`bRJ`TT?*{%H4541QsCoi>{M#vDTSw|^7h%}IF+p60m1~cj#&5070?(D7Yryf7U8CnxEO$3>}V77G6dbp zVAN={@hw5sC`47I&OpY;AOB7l6JMYA(o|oD-^O>@&;S$q(8lT}6PF>g*TO@m*CO_buvJ|Xd>U~}GPN4I$yfSE@0Pg3AN>8OwV zvLm^!MqNeVsq=!}Gn3oFS6N0WXon|j^f+OrYx|BwD{_%iLxn(?n-E@tsEi_Jb2u_m zdPtCMe?{OWnERQAQng=u`pa-*2~4T<>B$P~13dz#mq1Eu!FY*?1cx$&3*=Sl48SZ7 zj+XZ15y#`z%fqi2OOykDH5fUlqMh)|V(uZ()q$u`Di+>{r_gUv@#r`1f7PYJYW>)* zIdqYe01^2}VeWMeA8h~lXTdaMFAq)!MHFo8m!$kE5`}n1QIrl^%L)$43Jk^;7S9Q> zGx47V#P!kSc-E!eZn>YPqD_dNMtt6hBA?F+R6|j$;csNlknOiPxOH9lZEoA?E`zE% zpHns}5GjuwKdxTx;zk>_K#cFb7pDuRhxc9GGZ6M-JWrGL9@ISZ z7w*u=3ovAxE+r7&wpGW6F`{LIrN{hjP+xy4m`dls3c$V>9>-LfC%hi1|LJD5{p|bc zbIj%lE;AjYA$UYZib`(ge$5q{8Vnp=Z2$h~!LXMv3FN8XiM8h?J@aAkm+6ha3eo&W zQAuzaBkM1z7*vp2$7&Yf(mgEHFol^U5@W~nmL45Q3p{B*A$WfLMVLu0b#%T+v}dHg zhX3{xh7U}8qca~M@(b_XI0|P`)s}mNuKFP+3D#>)guJD%dv267x-=pm`o}+?*(c4< zFK`WukmbTa<)k|wC_*|tk2u`4n%%6feYc3o1OUsU+D*AfIX<;cVNGVn8uQ9JUW{`c zPOyjU&B%uc5r%$7zR4V2tl8S(O6G)d#a3 zfbo(Fl--6YdE`N)Sd4G=ZpG1f$7-oaZtmUn@}|?x<EzJxFS= zVM~YaTLW~y@+;21N!z7ow~~ruczz7;9Lcb4m>IhoNd=eoUgP7nKgoQ|kJW?s(WkT4 zgxmDzX+$z-YazY;55ePwk2lrob?^>h_l^d}Bl_^qOHbt50-ra{wT5+4yTm8cVr8CU zRH~RJ&AJi=>!EtLagtCZJDY5AZ?F&-wcIgJgs<(bEq8rva*nB|<-T&-!>=N*dn`Ys zJtLNNX-(>S$RMIiH!nwLZbpoZ_JNkWXWb%4qj1Vn&<7UD4C-6E#Q$Q)aXE7-=P&j0 z%*xv>=nz?ec%>_>fcrG+PT9LRvqJ#jB!f=QoIn-3IAgoJlJIiK=kvnE-+o zgx52GnYnmydi}+^`vF^}Tf_P0`P8-(4S^{G^X=2Hl@Q*ihJ6HX^VVFJx#oFE;i^4) zp4&wq&p97V_g+2R2FgNS+gozBjp0LTTAqZuc`4~Q^Y0zMkc*so2OA@=qxFrK*U3gR z8x_It5s% zi@Y#-TM}Oz`dT4~W{b}64=WU7=f5<0J^*}b3>*uTv}_7q7NtuMxT#Uk|gxGbkgj#CsDkIDs` z)(x8!?oEhBYeVq#z)6f}>3izeVrmVS`?pkiAsN}Zd@eTQd8S9cf3@pX|%=I~Z zMoX8(rk%b>IF1iy=NpkrJ#r@H=t)HXw**;;X`pG+U-TAT^W9H6oiysHpOx7r_@vE| z`@6cr3ARjw%~YN)0h^?i9)nB%;^^&kn&#`xj;|_5-S)?`#TnHcr=ADP+uhKJ?Aj!b zT^iS(?qh|H58kUyheT-))T$R@eMA1+#)3y13pMh)2MUrZV>Iqy=?uXN5knXRA z_+kvRUNekRRG*6Mzhy5B=jB$*4`KG)L>r;buHbrdZ?H+=47huOn&Ik9nmGRu|5pOGrtwrw=Zdr{~|m=K9I%0 z_L=Lpu6?OJ@LVN$SkHS)5bY-bR zI=Z`n5Xb~hL`t`mc_6THoc*m+jAt@`9zy=0#wg4Y{!N_$Be>HGBEX=7%n!w-HG`Mn z_q5azCGSm+!?vHOr5#a=#ZSd1-Y546_3n_5q_NPjpQr8++1U(_!S(q&^yUqf>!JHR zO)oR+t0Dq&OK*$J;AM2sqbm^Er!hdNV1@0sqaAKpo_dpw4LXYPYZgq+qP}n#%jB%c8=Mmdj*_?VGp|uTo2Q?0RS)&QM6VEzkcVh zaOgCCqnz~RKeb$3xAthbxLSEgUVqnj{B#$bJFoGTl*%@5)3n_D0Nr%6K=oVVVegt9 zJVM8Nn|pt|gqnxhv^BTFcfxnQXx|{V##?{>kHU3)wWXFFAOL`~-`X(7{|)P-t7~p! z?xd^xf3Z?EC2-jy^COJBe2%_dy`}|{5cpAO1vVfFOZde~)d~foYT8q*nmE5MKT~$N zj%=?-E8ZcV#!{Fg?D)a&&974-DRWAmDb#tuv7_Y~@qD|RI&r=1q}{bRaRMH`KORWl zP{H)d+L;C&|6PAl{k2U2C5r$o>no9HK3_baCdaHNO0Yxsg{1P<-CVNlL7(SU!js~23@W!dyb z^~%PAJ0aW5)3%a+k!&_ASj&m)QnsN=lSq%)w~bk^+|@L?R~mhk9&o~4mon6+UbwH8 z2MX7J9hU~_tXS5ZlM|I>s$3Ztcw?JKps>y`oEt9mPo{(^Fv+h7;_koScar6kr#9G= zk})-jTwpZGq?b$$m8*pg&9;S zphQ?Lrw~e6Np*;F4`m$08iWd}z2AB-M}lw;_v))3n3~B$fD{1+Y92&)!_~{Hn+bg% zky`0Xo)Hm%9zmf<3qcYdT0Wq4zYhl8G%+4HeL@OlL)Kr<@^F#luT)M3eJ3C~N&=F1 zv#SBS9%VYZ3~Rg)nKu?$Ex8zo0e&Jfvi5hUYX0lIZ=1J1lNb9+=kZKfxhmT*6D#}b z$}ZuFVW-R`ix8VjS27ymFn)NSV8qeq7!ZVj&*O*5o5q?rJZ(G9^z=c7 zZFkyRc7xB+LmMPF_RszGl@|`-4}Usd#+^a;muPUT>!>mjf?u3idYT~yjV8%NW>EJU z2ikU}k@Uu~RpSiR+Cnr3c%rqxwnMSDBOL`7NXem$$1yhy;u_cXx==W-NGl23RmKBg zqDS?Rz~J(R(CsJ31LEWtq+&p(C32^uH@ZFML(7cwXcc(qs(UO&G?lbpTl9rcGhN)r zrE~pYsCQq=OP^TJWtl)djCpYB{(1TZ*lIFq&keOEkgmwM_F&c&8MdB6W%jPfd z=nDCB-#hOMCsDtMr^%&Gb+bXQEFi9}TbRh{6O3z*ghr zJt#o{V;k7%+f?tn!1sv{c7jd<4yq*36!1M(FO6_-LD$eQb&jW##*{@z(%8ru>b&|* zkz9x)4N<66&Cr4Ft7*s=Mk@)`o{|*$vy&v+IPfSn-zc#LvtXsn80ldmy;NY$g^~0@ zVf*LvVP>Y2fg`K+m#s9Y6ReTCWqy<(&R79E$Dwd{hmJxWMEp{8ls8!agBm!d_8$ z4ahU_;%4kdNDP^u50}&SH#1Ro`!8EOFNeeN>sTNGJ(c9heZ<)(1aHgH-rAlO6<6Wi zxV1bsmU`?B<;4AHZDKn9G@WGxraOCym>d^x=7Pd}Em3zmvNCPFwu**45Ce3d?v<}q z)i`XxYlLyAE|J1V?z%TzZGZp_(p*lNm1jQDjr3NsRVl=m_b zu@dv*VD{k|;vUGldTGMluaV~^DC$i$4C4`43jpf+ERqG!h>3s8R!~KFo1vHA18TK_ z9%4yl-Bn!7OO-`j%%(+ts2ch!S3#jI{81weH0$`HidgekL8uq48i%P0{Iwrg^iA;D zYMQ?x;2t86*9AuwhCM-kWY3#kM#05`5~eM^40JAj9y;kbYhYDwg408mF9}9;bN~_# zj(5}4Cn+?TO3F3`MD~1>gg{YJJTofBVOxIr$tj3`K=G&Up>mF~aJVHh?$We&ZMM>t z9rmj<190desL&-$jMSy_@opFm&8)p;6y4N?l}j8nf}vTdd*lM+%W1Qsd_eQCx+j08<6@ zZMV`(qI_}4s+Z!Sp;zON5?uLV25{)12WELnEadi4^P=xU{fKoh7+~XVOi@xPs~kFs z94Ik;NyJgW>m=zIn?b78YJ44i>#EhJtOZ_R=E$ zgyo?KSg{ezd8og`_C)!>bsfTu5IdSo{_H<)NkoT4abl&>@PygpEger6Kgl5Brl&uA zq2g6Pt!hMr2oNR3MuqY+(=c<%+76fLKQ;3bDaV$QnQc6qXalvF9F+y-`bu({%j5?AJ_qPpkaej8K(f0r^ zA=2rjEaO4xP7?&^77VD*tagDaKDOm9l*AJ2bWkpDTI@;UQy2+X3;|FUo}ii9@veU$ zmbRiu53dX#Gauj}oo{9v+GhsA-=a$xM7PzsS~_Q(a`j8DWy+KyM#be#{l=WA%cZu$ zfvQ_XMn()58p2i=-8>B(&g1=?)mpIO%5Bhv&PzEHc}X$`tNe`5m zZ8wr@JQ|*EI3v2n#?Z?U(6Bz&ao?rPqHmk*8v$(M11n!ocM zck2dp2_vt}(mYJI>h@?kJt}Agjcsb%f|}a!J?Oi_RG=cgFjGH?7}Q}p<)Av{Zo47E3@93LsW{<@!vPdfDe*CY$`P&%Wk1$YoVlDY zxCa5aY^*sVTxhB2FRa)Kt}mIjX6s%Dp9Y;gO_07uf8TOe_w8ml;$Mw1kniTylNa6z zJ3+jOV;cscp8SRxgdn%iQ}WXnf^=p+tK2j2Zn(dd)A$4$39`WUSs8>T^i>?=n)(do zqdmvf7F5{*N5!#SZAt^(Fb-#vJaL+0;za3=)^RHUa3rg>a`r6ql9Z^4Bk>qO$U~Ot zsnZq^6##z%@~!(L#$5=(>FAqy!GbBb%ERJOn$PfvaU^#+PT?5BeI@0y3vlpr^5)X! zVVi1&IfCl#z|=PcgnM_rd#EfR3t8kd$+Nq1IR`s5lAJjkCo9_D<_sXWU=1HVmYQ~5{_+p@63NR1~jmJ1wr@w8&bHa9XBz#eE&`BVgQ|%L(J*SG1Gi92P;SyvJWJ2AkhxM)*LY_fGj=?|+irdJ}#WhuI z^;bG-#v#JE1I~2-tS|#CQ<&Xaaib(xpasMNtqrOQP7gJxiI@)C$Y;(E*V6ryKHjGB z%=@{tgQSGA*tYf%7R{opm>MX`s)9$!CgUx*$*Iv(l)z(iPQ6au)6ZH7iw0nKLwxhfc88*s zT}+b}oC~Cv<_9byK?YSxbo3$L&MCZVEH@3&Tap?wg+ z@m7tM6l8Db)w|O{G~UuQWO$n6IiQE&B9qee zEV8OP;aWrL(qpKOpg#mQb07*gTZN1hh_9zRg<6JR< zKR=C9v(l$F9M=11(`u&}+>@0=TxY^xn#R~cD)Svsv62j9{h9|;u94iLN+t^@_3+ob z%kwlfnLiKE+UFoQ`8KOR^(|1Er22P@M>#O2B+r9FF|Qd7kO(%uao=!H7ixg|LYCoA zewegjGATf1Ds?N>E_tgr2fi%sx5h%s3}(d2OpOMVua5~31984uQ*P_N8RP72-eNgdp^Um4v;R((2+5>tWAI&npiuupOQZ;>@Ed-GE z$SQb2I1vz-r1pvjAz|@jJrXU|p3m5BJB5UIa@-i%JUDn66H>zkJ-^4DtUS?xoX!Im zkQtOYf^pyUp&{3{FI!0 zA8rOf|LwWG`8t8ZlRDb&ekyw^8|nIb^)DUTYGb_XyMwpR2oP2+J%myg8%0~U*lBU! zM~`bE0{!W2_p-E|Ic^G>d#U-UzxnEZN`6AaIUZpzJ3Xh?2Vl{IR<5K&&wPwA(Jsu| zIHi97ko@*0u(hQps$k#H%B%VO_(NWfYnbF!=oQyiKE$76Yc@d zlQWGJFLcL9j{aY+J^U6;-WEo=@6GK$-m}kd{Ad&UR*)Mdr^q&pHZJ~!E?o<<7Ovc{ zSqrVFc}0Rx^Ly_Q0Adq;4v0Z9!{-wo1VGYREC6_HCHBXv9Gk3C8!17ca2 z^h<=44S2({;Ubf8-stClk5CUqJMui7Ke){b6v-naXeua9=Ak&L+ci65T%As9gq=IW^HEKVZtX@+)QA#%Zy zI52@5)=OjyBwwWFJ>&(yFPB7AznB36*N*kwv~4|024@mj%H@j7W_6S9==K!6h;0pm zTPz>F{f6P%J;G%p`mN}w!-D^YBehS+Z`o%vAGhbyARi>uyko6m=t&1tCiwLupE253 z&FQAS{sMfY{y`lpk@@*^@Q}s%b0m#*|Bg(wNt>|;{my7LJ$`TA#V`A@%C+6@=*!;K-^XKBCAd{;^p_@SVkOK3Qm=+rE1aW`AA~tc zynKpze;R2WSsUZs&-cCa-vux=zfPnF0ssITApii{|4mAEbhP?E$;eY4mR2~T>1r?B z9(b9AA%zrSC~Ny3LKYNpL{InYl6Qpi@bPGrHx{1j%XaOQ(JVDrHt|HRja)jfXfV3~ z?NMH6baO$jDNo}a9$|=DXfm~sonZ|O@%Sr(kp=wVKyDB?gQGc!%V02H<1fm8i%o^9 zaB<+qoMfGydAi@r&ns%WMW1Fa&O1>)uYZnbY`nMy<$nU|KQXTOf4<3G!vCeTi`=2e zl9!?27MVwG#5b2labL-ExpU^j-8+e79iD}^k1pUkCy*l>@0!d0Q`jy=*JF1xolm(J z{VHoW!|nV}KsOdjB%tiwjHh!WANCghPO1^{Te|ZE4c8j&8ItyJoxYKV3qJK!1papW z59RhB@h))Np2|Y}!=8yhU1y=q_bYE-w^qoTE%2Ca2IP891Z&ynp)RpHxKizkR*9sm zjb!j80&_Cy_3|f_-LSd%NNU+{${!wZW_|k2vq`!6Tb?1a0}b?j9irG-&t_cQ@W7Yb zX13*w>#64v&qePAUuAYInVd*G@mQg7g#=P`ZK{Y4D-zKcK=zQc@d7e|C)oiX_XX>5e9#?bGqeBa>01gAC}4R z;v~u3vz~C7n9MmU@B}e=CjEBy#>n2;{|UwSJDI>McewlWdULn)ZvoQ_G}mjR^uoPw z$njI!`&rr)mNv>~pqmaxrU&Fm7j$y?jlBoLE$6`fo7GK*FT0oiVaKmie7l)1 z$kr=5x4-c`qG$n=gX}-?X0&adiZj$b8>YI>JC13w)#B?6bY|B-X#3Ox60OO}Dh+Fq z6wsaHG&EY-NJEjY995{BGg%#+yo}E77W#e}X+GAG*g2V!WgUWdkEdIEhGlbxB_bjmyj?`q@V3>3Y%zN`D*w#vDKbX! zk_?Gh+#gpR{z-3&;bZ%gh16r;OOWw2QjzUh8<{D~kc5y>jtSRe&veq?g|*_$6xP#6 zMEUl4Yw?>Bj4B$B>)2L+_>ADJI&8fpUL`KP#LVmGcb62z25fz?qY};ON9&Dxr76#6*`xg(i%2vd#$6kY-*0@~rJBg^{aA8Y8Q%a#2tuWAVrZHw3 zYwcnRW4~KJj4--)tmQi090oJV(504m;idc4Mo;{J)TPHOU)fip*=^PY)oc%Va$ z4sf-!tCtZog-PuMU-tj=jGbFk0m9=VuZ6mL6get0nUH2EN>oBj3`|oPDHM&5t!HSS zowbv?qbDmOLR12_`h?1l(#gkNNiym{A0KCgqX4HO`x!mhr&aPO zZ*l~~g)1om)RB#NBZf%t$Jjz838|yUJV6Fz5^zy#YBcY~cY61)8CH`)r8(6RYghv(sg8W4x8(hTSu%4TX9{ljYXZm z+1~JIWAxG@o->da(z+QhS{$ZiiH^n>#uM$+YYxwu1?fBk77a5tOpdh_Gj4b~)` zbKV%*7o2tMYdgA4c6+>4!_Mdw8&9yl23M+`V0`Sm?B3!?0K-+Ez*O{wQMcJ@B{p$5Wsyn11>|d(|76P&!RRJXRsWMWC(gxOvGVF0L!}Wv}{S;IdCb>DdnM5sT>+(6CxX$orW@=i0{!=+N3BdP!B_7R` zj82-iysz!8OkL9PygMDl=6OBIQBhU-=X0rTs4c8*A~b5<%J{@=gFj*rdC zft87YsB%^ERvXV$s#F#Tk``^0UHX9 zL4k12R=k3Qip>*X#}uuR0&+mpw9|b4wra!1F2oC!3NSQeST|r&_X|-CZ5x5|M{*a; zlrEZtG%=bH#py(lB6C-mvzx9?-9hKR*G^QNvER>P-9>3jk1QL6k=VF{v^#%FA#^3P zktBnkQmDo6Mb#?0HZV~5dYzChaExyIXHJ_snXb|k@s!)hkCGYM^E;C}l! zyVhxBCHI|a;0BE2xu{v!I)xBBmcH9@JB!6fFdYI*b3+V~KsL}W4U!mrqCmE#koof= z54}FLk^e?>Cd>#KRA3@0Cs%DwXAk6NWJ*oBzRg+@!chd{M)Huk@{ogO)ficldmanR zj{It77qzXW-8F)G>n_qUw>ZBS#E;iwr_mw_GO7|fi1ka^11~mv|xfBQg|OgBf6i#|H^^&DBpHzJ$AtuL@r2T zy>ElpP=DX_eU0_*z(x$|5Ci79hv z!S9Px$QS)&2vF1m^nY&ElY01-BhgT<$-7sWu?sDe@^Xf32kOLaDdOTwUbVn7@G5#zPb_a9?KA!iwqJ^@fK zlACqYm%k)?X*B^S~&$i>hkALK;j6jjLr;h04( zp^!!vM^Nikk{d;}7gV`oAh_8WLneX=<{kl<<%<2nI0zhJ9gcNm$7{a>g8i(s5Pd41JX3j z354Um{8#&Z%9jU>F)PN%QqL+672Mp}(M>iqCzO1+4Sn77<%PaEEv>)Be`{#B&F}##B5EJo<`|blHm6i zG?VtCk`1eu_Px0kkmCnc@*X8kbvSiJCCJ>%M#tlOw)1;;?nz)ojnJtcgR_nGj%4+7 z86Gd%Y6H6O+MrfbznwOr@|}jA`@w=HC3FX`;iQ}^ffOt0Jes_|HoMR?*Ed3>Pb4r_ zq7b=kDbZYJ7Rk`HDr^R5)R0O;Xj3^Y9?I{F(*9j73>w4(+Wv}g6?jh#Ql`0AyE)*r zE6(xIn&SkUf6dU(&_{;vS;e8Hs#O`HBs|h4V^B@dKl;Z~j7*0$Hq)V@C{cX1Q*hh% zEbKwG`aDQbQ5nq+8jO{wi~7VZV2s0oZdwU$9#%Ou&E66dL-G&A4IQ0X@#@fVre%ls zO!smm3oJClO$rxnRp8{Z{@6f$%Tdg&O}@~Rii9=WS-A2Kp1J6=7`7+flyge=mchR9 zz+!6@v*O`F5*7#g%;GQJX0sFgpS4Dmd{X@CoA5l0apH3~J}w^j$F;x&YGi zs(?-$DBNy@(ECC#NkoFxeT?TD0aee!79-`wd{3!oX~%Ma1`#RfPGPX?yTq5Q;un9K z(aJEK-_-z(2$*!+UxSE{(LlWHmEG{7EM#;8f|K9igTU5O_+wC<(E)414C8af7tdhR zTB(?XkOI%7R+^F8n7+5ts8_1(pjVDhPO9l-q@OXjk;YJT?o1Ju9}M-blajK1OoeAP zCIL)#bV2u*Knx@NyMslvBZ-i(jBio(Iyux98i$}D(v-u{3P?=${A*1t!N#$yoC*6mm4xca#%hg_Mz!tE9ve=Z&(XS^ymd*0}wNhl(Da~7RkdqsnwnZT8 zM07ga)}|?AGE<*#nJ875QAm%rv8Wi-A~1=*$6k#Y%28iz7|Op`ZOZ zNmAEw0ikOqsiKuEablE9Dz4DaWaVVHaid~ov2YNe%BnLx!VO^_>@@R0qMvx$`h8(Nqt;6tAFyEf9Wsx7eE1MY!ailBqo^B+5L z5|@L>V+~PbOd8nX1wdnL7QiGOkVB0BZRnlSU6KASkFiUFeT;*!;CoQVjMi*=f@e=% z>TQK7znd?wj0&}w@ExQtTtOnrMeiiGB4KW6JrYvrozDf%yC*%C3iJ-;#|!vFu`jvg z1?tSurD0r2oi}S$i7k!0ULwy?vH(${it>YL^zE9MIoqF8(w$jMNmkej&kfl~@u5o# zQjG?zih69ZCd?QGFY{S>wUFmH-Um4p^@rr03uOn9l&M;%nj5ky$=Ydv{cF+27RHmG zU#|Gl*^mCBtU3kXHTuRWI^|pwUnC+#=5NzRdoBn_c+Y|4%E}>lY5Q?YHNhpvdjl|V z(QhLe82q`2An1 zAgq?n4yoI$2GHUytN^U+cUkNXK#)$iy`!uYC@F9h``3F%+vzP`diHh3`yza-Z4K3D zd@mC8P4e(-@*O>k5w+j-GqG#SoD_Y#^KGTl=#v8nA zQhDNv%UZ4ri8w@0Ou1CQn(S9Ny_0h){;)e+j&YYzucswdhR-QlTU}4r=aPnv8kBUA zaB1Q}oN&^ZB+igVz=xpw1|+2U8}92fuya?~?K78;8@M?H5hZXeqwJ+K6U?i;c0Zgr zG{3f9o5~Rbp{10?+~zf?SMviBi z!VNaoFLh=nkE6mt(s>;2Y*x$TfKlUBkRi2uXNUcR;RepGHPCs#)}e&oGg=uG)5NQt zA~y>^q_0+}xwkj(tXG7CADjSd$F6TsFrTo#;x>mLsDK=OUpD2t6O7K7ikj(qJ=zMk z&;f%w_pl!%g%kj6$Ru@#&^-5_FGvGpw_%k%c%Bx{syJq!2f8qyFM~n|IFi^3gfslr zWR{HNCd`yMm?C$CG!N&tb_-pN>7<5}&7S(D)uM9;xUskgvF1!!?u$GJXAciF{<`Nw z&!%$scLCKTFPOxZV-G(nTZ3E;!U<4stAjHSKYXxdbHTm|#y6V^4r0}j4NDqpY2TG> z-a(5V%<==i$Zb!LrjS}Ja7MGCSXXY-z`C#z01@cMwh~iD(~73{KC#cfc@EpFQT{WY zt|*66q#9OMHCPG`-`Izf%%u{u8&F4nIh(Ay9{bbdH+uwKn2OFpDAxl4mF2`(i)DsU z4YdIRP4(f^+6tO~iD4{?4ng9z)OD=Vgt4Q3RD{XPGm8A{U=yadnHZtcNQD;hMC;G2 zOBy?L&WKB9_=PvjX3sUsJ;Y;Dkt^#_HG@dz+U+|XmBL_c;zDg*5$efbQ&?YJ{{qVw z2@woTP35#$W&+{j5fNZ=a^1Tp?TD0|iZp^0Z=68C*-J0`=aeGRrI-VkK=QZ@=a^$f z;36ejQ`IGJLqW)LCoO=0*Oal1bTR-9A79XvxO$~E-Xvhs))V7jvV&X_5Cd%_2mgO?(g2|ZES!kX@D&CE^h^cWih^d5W#<0{& z8}tX>qoW?k3J>;0&}O$hGH27CxVgQczHjxWWVQQ@v%|-PpjkIJ$ykKS>m~BU>)WY^ zaZ>wh@Ik5PQKRMgF{KHYx903<&m>6+`2 zD!}Y#nHJ;q%XSBNiM%SNJX{2vXK2kN5{?^n!^GWeG(J~eo8jT7_Ad85?M=8WYJ=g+ z2#wh4jM%0yhuI*NVkaINGWDx8PL*TJwg}ppr?Wpy#|%AU zxyRW$N=goGJul&JUI${{|F%BLYxe!se4L>={5}`A!^eZXECZ1h0RL5<68u`gn`5ZI zZ4e@RfFRcv0%XyTtW~(J|5~~)=EKOM!{yN9@xtG9l+rp~Gi0G^_f=RTJv@G;Bo24W zg3ow4)ib!2vAaQTkM{Ym3FuX05P%CKI)*rg)4ge+=z{k(TaN5BdF{U zyap}l%;sv5P=3RwMOv{|Y%b?PPO@&4P@tz%{Q_eOwSMrC0U{3zSgf;2g`cCazKlpG z=H-vPVt%kBkEI=OjEuVDZjUBze?IXjg$`}G~72L^&~Jg01I$zWtysb{{g%t!@I~&!l2Y(|yePj)my*-1Dwm z8DtE*$GTW8Ya2HAtFNkmmR*X5#snUPAXNM=C77<9vbn}?{&~l^42a=RFl#xmQM|{| z8>P>{GKK#8OIN{j&O*Vn&jI_@H>}q>ZVQDeD%b-wQ(NwFX489dk;M+nSElTm0L*V1 zwnDl36e}O_Xhg>YfmhU4^RItha`Vr)ibpPB%oM&LJ#;AR>+ByF#b-8n@cg?b**|y# zNCQ{Qmk4(Se-O_!u8v0``B6M*9sYnXwS_4D3NzCul*a-iQpp*5&6pr_{By1 zsw>7v9q6kklZtR1w^RJ9Vd$rG`^^Y^6}>ykUa>W$z>lCIld400WoVE z6{DJ9jsH#E#B+Nr5V%b;xJ3}8 zo?1-sD8?hNH5sGFnLW(FmkZt%G z2-4q-S<(22ZnCbMUN^;~K@M_DPaz0EY<`o*NEFV)WPXTFW@DE2>Jueh5;oOCM9ixB zCOvSuD?;)ea-Qab;BB6!3j{o+qF+|+emQ*u zp=pbUy4%uJI+-*HJd zkK0gZzV&KipRQ<)vj}e0n6I$p3~8y1+x14lkGoAGkzTeM>1ZsWLTKq|OgX>P-9cT! z-`ydp)TQWsyGVxGV~f;6XzgOV$`YWrZ@9B(al1&asz?u6NYMIaD>D21M3^kd>14a+Ftd{>D0ib`>*kKpJQ?TXfT zTo9jzV?F7-<4`Z@++*dgUcswiVvhiixf-@v)`vluNS5(6KOQqTI9d=N^*q%Dz0}Zn z1i`EH5aN)2U%7dS1F+}kufI!q5%u2?oBV2~BNsR-U}(FP)MTUu9Rp9{W4*XDg1E1u zfvuqd;-P^O-pp+dPZqWvo6J}DasE_ zdHK^-=`@?Fe|(Sm1<-l@R65-SjvS7t#dVE@4FZ(bW1fWE`c58ux?~jieTrn>@+T?n z>6tQ4*IV1i*nA(`&|Lo35hq1Vub|RIUQ2?+N(-;nSEy<`9ombwLg{dQMZj}%*|}c4 zjX-`S9Fa3d1srEtbhQGuCg*Cj>u=I+!a0Ghy-uo&Ak$fn?RX0A)WXsO^12xghRZ|Z zDnt?-KB+$dH^&ROD7o3;!ad!FN|7D{-CF0JwOxt_9na_#EcUYecPT1r zF3U>j1@da(NjK%c4JX`2jOd?oWSXVf)_83S3BTG)Y%Oh01)H} z5C8z=cOF2OmWShpNc>MujzM(yFIwK;oU3f>HaR7MlvAb3u#LpTQIi*+ohXDvg`jsp zFou15rhAL$29Ur*aZRy}1nJ@Y?aA>U)^_4hHtAS#S%v}$q~uFTekJD2L;kU|#^I7) zZgv=tEb|vBb@K+x&Up>#%*xQVC-S>u*7eoN2XOT2Zn!z^csoVC1ijjWrG1AaGvP*F z4TE(=e9a)Lt~UMd8J|n2p?wPyc)r{VHY~MkdfI)~S3I!)N=XAyZ$*~2f=2#mP zth@c1o8z&ui?^|yneA_eqA(VK+04)mjl-mlG08YdsRMIA?#CF2HxIsJ_M zz%N`qm$(&%=q0Yc=lo;KOI54fyB{q1B~%u>ztoVPIx7Y~rEAvd##YKu zI?w-% zqa=ztt4Bzu572b_2kkfv3c@j)erFWW=ya_hGsgEo#9aC zWq5G~lXYkF;A2mk?1-4GR#(F_fC=Az~u`Wn`MP-sEYE7G|RUR6%eCl^&M6V&> zt1Ohr4&m+u;P`IdTr4z|ilJ?F9k_0KJq~UBz&1v) zS}=#LHHf1)H&9f)DuZ#ix0`|^&@5&j-J{M>)oFeI$caTzo{#z}D+u|uZ^Yxn@!^!7 zCmwpYe{z+oC+l?CS=n8t&68T*|E4N>lW=v`tD@~W$BIx_@eM}9|1>%?ml66^7AN1F z`K5TuVrd4%j2D9ifO;FdH}oJCHL^SGwd-+Dgm0wM_D(3lL@r8H3U?!s5LNmvN&2>o zT~Ia0>Og4$vinK&XziaUT3vnT=qJMi;)UvV^}KzukTW#Hs5y9*{o%syiEQ>nJta9 zij8IBM~KZD$KC&hsJsvAEJ@!(vxB#jxmU0v+X?>oB9D!H0MY1?j$>cN--$vE(@v@@ zPwCX+V$g?*V$>qzB1%OC98qMR%b`)nvbh%d;8`bP7|;ykS+~wkI_m`iMu$KtpS{P2UL2wyeMRHBFst$ z0;%cmzVVva&UPaoMqrF3GkJQ1Iu(gY91?`K zf)0>ypFVf$^ocP~IZ$dM>m5oMA%tMY`}}n$0)7+5P9AP6l0BI=TzQnU3)`8~p%5f9 zM8VGUH8TTw?98%gmu=}-f6v+BS2geViEG}USpy4>R}I`NV@Xc}0GNMgJfCZ(;Nx5I zKiEvFh2kWn)s8D+YS1rSJlwwq5Xy&Fvjug(2tVvBS`bUA7!BVd7fu$pCF-hGBWH4R z1;J~9)wCd(rLYfbT69~B0W4GvZ`egH*Pb)Fp!@rHuLJIGc&T=XfgP@EVl8EfTxh(8xft38#wKjJ?yOIeub!QiUiSyYee@7()u@K$=h_25l?}d7)!| z2>1#n^&7s`f!eR$soeGFt*PdPk*dj77rV(uSgGy+s6;lCBm(2~pwNuefG z%@KUj2JxhfA0CSKgx1TYN=obk{}*HD&@EWlq}glRww2emZQHhO+qP}n$ZOlStNasex2U_N&6`Tg|_Rb2_>u`CWR* zg#4bV7-WrcoB3Um8ZV)Rr2c9Oi}}SDNv>Z__TcpC6Z_*^>3wM0RQiO_U76 z$KepnDS8wZj4?*?t9p)N(2K?dFY$83`W6ekD?^O(XYKvU4qEQLFmxEi_)lOvC_^L#P zR5&4%Zs7DDhe>i|Z9P=nxaYK1ht8_ZIM30GvIMrdf*m}*e=;MBd+{X;vm>@~sdIZs z?Dl@wIBo8atCh5ILyokA48z%+Ua?Wz2@+hzS>l95O%~E)S*Thz+$@2e|0D`51lWZpH34=Qplg2y)7x z?;JxZj8f={g=dew z-2UKcd$&G_H4z@hs6PDOT>L!4R0-0ooo``3*;BidC=OpvzczL*Kd%UUbo4Ru$9-@t zoR;w?;JEmeRFn=;c0MUKZm?a{b|LYrgk{sxrK&t z&1qxge!PCqRPXn1eF3Rvv*ea=FG!(V500?$xA66URWeJSzy0)2NVf|i1e*gC8d;@_ zKX$g1d{P_&W&HM!y%zf69YN=mQb{o2$Rf5qNE>5X+8JZWX-Dg1ss7by&6d;P__d$U zNDTh%g9*3U>PmCwcC#19+$G_3EzC363@<2^f#bHS^GiSMwOm&`6?w`{8WP0ktcW7q zTBV!ID9IHafo5}uPztX0dMyYbw?Sk*Jk}R$YesjJC9%x=mJ16t^qH}J_4rAC$?lHJ z{~1V4Kl}Y>K%jgVp_wohv34j#{hrWJ>{r4o%hM7$sg+;sx^Gi5y>sr+Wrq{q!CPf;jwB{(X==cxgw9kh80y z%h1(?2T9SH!ciQ|6ABoZ+}F*3gb|v#ikq1{c^SDkwCies;HAba!qDJe#IBB51{1bj zDvfZVUJ%4f@2GtuZ_UL^$Xins;+-Q(!r`_-`vzYOCZ>xK?epINE#7)vO7u432O;~f zo?ezt-|XF_lYO496kRA}MBVT;ph}57GgX!`-Kt&+muDv&3Q)b-Xx~s9DwivXUv_%I z@zV_AA78HX>#54dT`Y+AYAN*2m8K8oOJ#1q=sKJxIM|CDcq&F{7W1$A<8+rQ#aRmU z?!5v&!Sl%}m7;hxpF+;hXwl%uCyTW!ri=BMg5*z)Fvn5&WfOFxj+F8iEJxL6%w~NN zYAAx3r`PZWyi!dtzy3_JRC9vyTJxoT7`CEDL+rW{l_uN)1&TgIQq$;X=H%1d#yZKP zezlSsRgA8xmZL?Brcqz&3t?!!+}L7(!(kLRk7^&#D@#V%YT*;s((DLF;*JSet^CrG zWZ%rGoJnurVPCYX)X6F$pGSf^3VTy|_|DqOVW`wD1~vJV8RqC2@doP+x<-$Gv1UYL zq#5H4!BR)Pb%K*ud;|DOUTlBgek`XkLB8EaH`B2wXe-9S^-*!+#cEliiR=!{=YBwl z)`c$f_R3c&laQesPx88d(_Kal$a1!uW)Qv5JpLtF!2F6Tw9Cczy{$6kG^hFz7<(+R zs8D2);1k;$rXTH;y_X+veT5L^c!*(wX+i%?Fnm&ZJLJk|Xr`%GReMkr5TtZ9gM|&M z5%hG5hC?wODlZ{0ByW~=dFJuZvQ_bM`!RSZR}99Z-#@-#?`DrUCvj7S{eJQ-C%q;W z-ST?F;{W)ZDPXAMiJ;pf;|6Tbo^Wu*BCf%HD-khOFll5-wxp^^vrnw(b(S%cfIm8C z8fqpqj_U%Rn=>izaxJ7Nz07O8Ez)B>SrZWW_{EG)EZA5?dFnfrawdA^r@Z`SIG%-cHQI9jdR|{ocb+}Vqg_9 zZz~)PUh_k9;A_9k$2q!5tOeMH=2dFERNL^UugJ3QykA5|*H-c8u3d9H4=2?{w_prh zWE53=I7x_9RynShZ9C|7am2SXnPGk4)D7{k+`_u#nhF+{FZ$D?FH=2sk}>*EhE8hKjM|Ab&p|Z@2)9WuEwGaKVjA8?*K~&k`bX& zL8+3~N)=XKNKx}#Qd;@N%0j^V1&1{Q2ylkSWyc8Oi=jD>nnLRS<(zfGos}&#ykrAa=rPmnD4Du~$Ex@O|Pciyxu^nSL zJ1SO_#{b18RtNxA!KdhXFRB-?-#t1PGdv+%T}ypLYNiz*yHyq4_&^}XZMA}P7wSa| zN2Isd%BqUDgnLJ2wbGc2eSbFT2$r~%K`e5dl4nG<9Y~J>$}!E=<>sk-ZWI`{f=%d6 zB3%BI`{KD4yTBsPtXaM4aFkE5U) z8c}<@e3Ld8SpL{m$(V$VAI%O~NEbZCSd#EX7u7Yn&VvPbu2I!r{%o!5qS~IP?dYyB zC$(AWp2(OR%?}3{I+!ZztPPK`gU^0rpp1taaC!FMnX8%4P>xhClD3?bv&r(T)*0(g z3mfvqoYLCvDvKM*-^|fi6i;ff2CXlI$Y$v2H1Rsm$O<4g^WmRjCgNcZo!dpXp&ehX zS{FO4ZjM!tx)0KT7To#A9i1vY!P^2*JDL|)u$$`I%S0r!ov4DI39Vl;vEB;usJ?%5 zLnd=59g}Ly`YmFADxAi0oJ|NlSy2~LM!nL=+b{RJsLxjMaWG;fXfHqWKF5mcd&M;k~W?>1-r4x*=Jf#&|6oVmqRYFKO zaU`Za0o6{AHgs9@7ZX12%{+!>Gg79?1|>wt#2KRkg%;XZP5xNR)Sbkg$N9SR>MwX* zji<$!Pd?sZHF9h(oZ4nrLByOa_|t1lTm?NNZqwOC;m%QZ(bhEpCAC0>koKk3ZXU~HMNqVOvHoPz=Dv_&1V<&ND0=b}j2@7Ul2B#fT&e>{s7EXjL zPoH!D4Axzj2)@8w+XV<{?$pjrD<&ng`dGUSPN(HdJ!afaPuA(hgEd3-Auq+%jTiTM zuob@W`OGHY6bf;?;es$nQGDA3;^#j0wCg2C3`V0{(ZQ1apn=>M`q7F#yeDgd8DaYJ zJ>i^fBjvg}C<9V%Ljimt#a%K@S=`s_%2^k^6CW10nsq!oaS1ZVk)?9$Bw>67KzxQ~ z8xloTok&OphDMC`l6{R&Sk!BMInw>;AWF`;5OW;kPOYz{e}%|R(u8*WfDnJ_{kZzr zxg<`|(NEER?mmFfd!f!??xtRY5{(U1{yjs>a*xMd?-T3PGR`!&wcA4@H2Ex+GG89C z8=NF{H0PIl7zkf_2uN=Xp_QXA<@!Ce{t9L;LA^MvQbwrqo5ZttS3A0wGnFJQhNg$;!Rk+iYnmF{$5Fcom>+KJ1e zjcL0xf0Pz0;>B7@rhH9P)CcAmR4_+$5nn zJ!|{EqrmFa!sE?jEBxcWBY)0JiRDY4Ld`BrmVt$b*k%uI`H{NW5U2g_?uoFzfd4x( zeaYgMq(=q-FyR6KK>kl;>f&fk_df&E5-u;j&GDAsPo=3yM_STWf^OC-t+e<(k}afs zuvVLXhDM%Is>Y*b-~)i~tJiEMriJlD|F4xk*0lwX*(r}1*+TnKupo)dVnlqHbhfN8 zg~2TX2xPYVK>}Omqo(jGy9vV^(AAhxkOZ47Km2=+0jj4Vd*N|%Ta-szl#qmA8RS_t zehK#Qr1E)ngWn?>m@m*SCt&ZXK>Ey%-q)Ze<_9C!Q&Oe)a35YSE(SSTw7635Q5*i? z)n2wOV_o`vg{c{@Fd$B80Z_=XsUrG4v;|y8R++?(Z44aT*WN2!JnwBUzct)!-?y#r z1s)SzQ>?cgbA}J>A@o5J%-1gs3}BUAv;AOFB|Ilqx1?aP{1@CfcI?OTu45z7OdtRP zmZAys$F?>u8CK}AidozPd@d`iRRR;vX0d&1n_hr`pciL`)A>wca~Rpq9{BB|CV@I= zM-d#t)7;tVarGYi&7_`}vF8`N@6Q;qAxm*sD*e8UE;#?+H7mdC$v*#=&TYSA?rM#2 z&mAs`eHILFkVcIF8{p1k^{&uh;QAJp$Ait|)nqhmU5?Z6EeRcY+J zuMzVIvn^ge&Nut+T@&~>qu)2{+q?Q@{}c?I?5|!H_H{0y1jOgStGm*ccXtb|*Q~gA zyCQ~vl}HO_x#LiWwGOjuySv^$KMD1OO};7n`Yd{T-QUUDwdK(a$hfwQj8)3-&?)t35QeRI0-dfoEpB#AJF`=n z_j?vCE1KV}E~!J_t}$xWn6NGiT_24~->z$8#k<6?Rc73(HlVPvm|HEwu)Tm;j-ry) zdd_~BRmyIS%>Ikhsy7IjyUMUuD=$*krzF!CyA>-7clI=(k&XY{S+>1%yZt?7a@F%& zzx(}m{y6aa{e3q%e?P(JS!Q-pPpZzjP0OB|W>^m6lgF!qrZ zGhK6?qqqBG3QxEB%Q6mqZC*R1Uzc&OzxE5+)8G9zqncq4xyQ5S1|cWS_2dH`FhCC1 zi-+$54*rRivmA3BrUKoArKpXDKlcGIb+hYPQ@p;pd+Rg3)Qy6|$e{=Q@95Q~_ibyV z=L$GQ_jPg49R48uMyF3lSNlZ`_vwmsr4HcSy6&|?3$?U2!)oCZy@%J+%l>8WlF6<& z+S*&G(T=hQQIkRmzFP?ljnxB+v@KK6dw275j^`>j_lG$m3bw3geinj~Db0TRLZ$r* zI?J1vmj^o==XK^t5P->v#W(-AfFb8ut?OjyV`v0h9USrIu37P`S^qNPO-EhUp^`D` zgpcK%q1&=Mt%NRexiVs2^?uIht$TH7nCc`V{z49)kJ~5xKD!V9$_*aA7eyVT@9Wqv zv-J7G<j0E_!rB_>^&Ie?W7EZ=R#U z{~&b!>D7dtMD$ujXfWB8$#)kobPg6le7_5yB_4kZ0y(Qt1et`*${CA)=we95Mg+g} z-|qC46c?V%C>|K^$TT8k_IEt~YkDh0VV2J;{~UoGy`F3dt)H60D~ytl_LeRVi)eQ= z+I zY2hz7YU6)twu1TYU`s_<8KfF;9VLQzW$~31v{&kh1mNP01keT_4$7jB>;DM@y+I)- zSiY?QP4Wul+YRYZ3>xT|?z`r2ASw|pQVL0~7o-YgNTMeM9>?db>DbYe9!@w$)abpd z@LYp0s9~Xr8NH?Fz&SS7swuBc9F^?2#VX~oRsMnn(Mu;GVwoWH7 z1+W!|>EvhF@UqLybi6P)`T-GUcou7Wvt^Fc#vsb?$n@m>=iiHl`@(7CiCy_gW@~LX zoN>EtzqVv+Sk+WY4Mt;wk}umH9)#rpG&W8>u7+T3hY{wRY2SdROaGwcz`56-fQkUz z6G#UrUkm{Rgn&f>ZnZ{Hjn|6;VyT8O(H(lcJ90dWh&7oBq@5Gn?r#91>g@{JcAH)Apu?4i*%ytEHYYi0%0eqic!T{bREofQ@p(@o$vi8v)XFZL^ z+7_iLHRa~qkJza=6&=9L+&G~N#(h7O;7RIxzLIt;pwC%f&x^_NUfqtgFeeqFzF{-?`uqqdsqVIY`x5QTjJ=wK!!=ie6wN?^$w zuT2);Ek~~|B*mRwmKSPd!qwE)V>>$yB~jxEWZSt~sgG54;k0AR4W~!3rhF0*IGC@SP4|u~>XrPo1 zw8O#~(RF9SoVMc4&X#&h0ID;)kC@&B;*21y+;(wEeuY4M(t-Iv-GH2cJ~B(#7Ys$q zX2?asDyhc^M{v+sM-~vofNvkYv}_m7Scu9x%dgM51z~Sk-VML5FNJ^%0v4F<)8cDw z{9O9FnZ^5Yz9Vmfy)vF$6IwT8j~NtNty9o6{}b@lTHkMT;Ml$gcJ_Bz7M@GW%+q(j zR2-;hb>#M$s*7Mbu@^*m*a9c2xHk-5SSTk@NIK?D_@8rg0>K2#;5a(5w;2_s8?5+H zD2ev+A6;{PT*Z4vmR@+0xjH^o{%EvXXUB=Un0}NhqC%So={mV`vXv zuou#g5+u5ZB@w`QLOWuV|2DRy_xB{kH5)Fd+v%vQX@4!mm;_VcUfS=s_h`d1ID0{$9+BlnR=TNsXMtD zPytoQ8aJ~lDQpD$TpBvzHiaX4X)aV?XE=I-DyQuL^2~)1+L$;<^^L7@)^t)esf}J~P0AAO>~RTjj47}~dd%T$ASr88o_xb2b%Qc( z=~u#Wa)Yz6*0H1rT+vmlMdOpIfe* zrm!ubzi~VKFX-i!cLN3b2Bq>Lz|zR#R}RCQV#7iGQr!Fg2stMLLTkSK{*f&9ERZR9 z!Krq(63A*=39D_+i?)TogufuC2!vz0Ad;c!r2&VCHoWCHn(90N6fE&%zF4IVy;7+7 zYw@8r>~goT;W7h(ifsGE^%cneG_Fl0s1(U_F%^x3fF+iw(tZM^liHx|wB_kYOAE&A z{V4!3^^}#9e~thi2G3jX>XI$-W+YH{mlc~(M}2seX5wCY;nxEFN~HNmr-&v8NOlnl zc_{!gyYC{QOKnqIjD&ip*BEDi40Xv6U4L9lO;Cz6}`LEZ>{k7 zC8K!`DFlAaG5*BkxT4Ml_1%SlGrSB8gx|PN)&B~zIMVG$E&;4!3TT5_M4cWY*9!l+=*e44aWF}h=G5q7!zPeXx{~{Swg;rk2O#;cGCPPy+~WPuB`fjLWerV z-|P+3B@j2|w>%`&;tJs@T2Nd3Nw8N@{cv8=)Q6YG6c$CB-R}2RA`8Y>_Sz$)VeBHX z29N)Yk+k2VWq_gsz9P>FEew-I&r^lgIHKv(vF7}iJae>qw&1k0JUfbCR*CiArIK^3 zvPpZzn{b8GD6TW5j;nf2+$(a2S9SS_JN}XG@k|7K)=VX51w!6j-78sE0soFpk&tw3 zjI20f55A-Mlj*+~Kn-j1aTEWEb`0}`I*S=Wii~mBG`Xnw%&_HtPCw*T=WKw5MJC2Eute)O?(2kG>4it_N&vcI1{Gbu;j^r?Y5Y!-c{RLT~?irM~ zU`()rGHg(yB-y(~F@eOo|4I4BF^Q(Zb%Ytz(8_l^g7d7_@089kyDEAS5rU$}qo61O zG!90s6M!(Z1RqtSMCfbd6gm5T_hLG(Qw02$%={aPhwe{3FvO{4R1y`T3tO;vPL|3#D`cr&Y%9o z)zgUf4cn$Tyw|Zo+4=}uo~bxt& zId)(7>zH8?>kehFhG|NF+g{#hIe2%In+LP9;6CUMU|(c$}Yr}l^ycEMwyKb z_1xN)nw}hyP9zDTZV&~OC*(P%u`4J;$+6^$ILlxmo1JEye^7+ z>5;|=`g|jPHb|jeLTsLpRuwi94g~YbvE_@xnu=iG?2=>Yl}1!=&P^LiTrcC=R0v^| z!l?o2q7@98lEaO^nu5o7>~d2TlZY!=ItLSA=*J7ucxH>wf7wdHCQ0+|kezQU4hY%D z21ClHywdWe(DQ^p%MTip=1Su`&xrXJN_KpEwY*-1vVAlZG}Bvjc-2VHVxZ z)Jc^gqyM7NiI+d4lLN+7=(R5Y1u}e33SDMSd|hT1N}GhCG;={n{XoC%O8o#7gv@?U zosa^BzDScQKSpfo3@iKAQm-u=E4%0iKy}t0*{>+}Pe0-Ov?31+z2(~6%-U3GQP=5J zY4?*~kxD8q2ZoK|O7S6B{-m^>J8&rQ5_JEF+8WLoD&sm)CH*^V?9ze+xvG@^OYYb$ ziI#y+Z9l)QNJVMMAgNowKh zgU84C9#2T?6Igu#g9FD3nZA{YfEvCh)^{hBirMc2w@t&UeHe$?UfH4{RaycN$(YpFz? zS{iIeRRL3ALJ7;Fo$5({x7$3Gn+|Dbs(0_mf`L*_5WC(Mt>Wq@e=lLGIuK1YO(rRE z{EF3U(8y|__hI|+^Rd6kog>F5cY}9glsMy;B{O)bzo&b9Ys)Kpa=vZN+tbxUZWq3{ z;mzVgx~8U^$b){&?(@&Lg{N**kFv_5N0St1U>LCho|L%4jB zQL8=*ovR@iI#l4}YhX_TaH_o=oqua3+8JwcP%~g~EL>+o>)05~BWRgArYfeWAi_4> zkz^cbnL}eq3ZmB=sb%7@I|-UFxw}8xej3;KgRmE}jNX>egwxMX{K)dAykv5yM@9{I zqhXHgabO95YAlp-J&o1c6Rn4ALjY9iI0qO@S@{Ao0|TMvG-T+QbDBRQJ{UumStl8( zEdtz#;?-zhsdRqU7A`QYGXXK}6suQGfSp0y&CG7PvJ{VNy)Frte$NQ5njdm*?Ipsj zq*cmXKG88l@&$V`aOuX+V5lkZfJ`1Hz|J&gV$VAd(kc-;vsK8)46^dfCHD7`7 zDE9a~@}W5&o9<7q%$Hig?prM5Ma?L7>xBykpfyPzrxwK|N`RYv93(o?Eu-rLQ8wcVfVh;yP z^!k&2G`^G3Y(D;!6c%B_z@Hy|lyvh`i%^6k`{Ldi0d?q+PySHv)QIRT5i&5Nc}zbB zgR7b(ui{;(dFTE~;`m$Bn#ix7-<3k(AV1A@xpDBZXj zguZ~T9}CpEUzgZF3s`5c4No*jrspqa4*f9$9G_WwYkmDOhbpABYMxJ^4IrLgmr!pA zau{G)85!t6v)&D{H!l8@Kk((+#b9x%%_iR!&o_ZkegmzVD*eOke8EAGuZt%9N?LiJ zlQW1_x*?H_82DaKh_E{lonXRrJD{!-dk0V_$4jPl9^?x0*^$p{(=EAq)yNp~*Ub(i zdX#=n}7Mawe{6GdMAp ztCX&irDWa|dy7zt{m>(&DeI8L!fYDDJ6lmCwt{9nKzBSTfIdF=sg{no}GsP*)m?qoN2ATbm*p~Yo5Z7ogAjhqBFXYKi z8GPGo(?krmV^(iOP{Oi)SY{+;w@ZJ7QP_NRrL_%Uf9e4iESzF1c*x7#(^jcSGg(<% zk_W)&(6bL`&bQ}sp_^d;`&y53yG#J8Ri%=yO`Y{S7#7CzP8LvTnXQ;FRhZ+oS<-^o zp)4UBTgFf(?$tosD@ywIQH~o&b(=s=3uD0kC|BZp)O73tsJW5@;3-z-M+)voKJEyv zijvVEB-S1WfWiHId_;^(31roUlKuf(I<81Z9BsC4<7v<_Sl4h+`b&gZTjo(^&W>*F zfV^iU%ZrnRU8F2C6=(;K=YtW*3X`4%`=+6KqYBkD?aNG^@3+OOw7Op-xvQj$3*nA7 znSs~m)1sfy84e;^xTzC80>iBp(*-+CFfppSR%av{M<;E$^{p;^#lv2awV;5$h+o*c^!aY{ zNF-{{l4?Pu&5%D1a?+wTZW=X}$mUQnXJr@;(5=|U&epxT`HMhDR5H#Ow7Ci&UCr0w0!a>H zFx{%T@^awWy0FUv`y>e~o~qy1V0f7XeB*`{ulFAugdm*z{J! z)@1HAP=^2xASF%)V86KaBC8%>Wf@>yd&u(yOhz#=5EM~?m5lk;l>G&uR*1aXE zoNpR6`){o;`|sO+a!!sO(|&!$lkYQ5KM3`J`kKhkO!)UeO{n%iS-kU3MG)QyR}M6> zVwg_5yTzUpG($)F-)z)13n0-mm;smFq3u798-zA!{);b(7zKO^$Lrghq?PEfwGat^ zH8N&DK%niq)T#ymoQqf{q0*3FTMMz|S!csB+bN_fnrs~(^?W~NQur9-st@>S8SP89 zy0f^?zQ2pdpccW7?;X7Z{)C>P#<}{bq!V#atcmiXlea6A+XHJPaBjz7|Q<*ko z$2|{W+5m1#75S(}YJZ0wY6x&2KOS1NVNI0Ch){O5aCx;v)kJ?g%gSp2^7!E2kQH}A zlBy8UBYYtFj;&j^Y2|KZ<%TFxlfSoqL zCPIXCK}d_8&m+9tL5t1@Xfn9^4>yQr1i?jKalOzaeGMZ2SQR*@6+aCYs_@mnv0I^-vWh%62svVYL0BSXcNxsQMIz#ODE!AQU~K z$v5&Ipu$~vu;&u|*&Nt20ru|C^#jM^DYXRhwoFd-*KLDid>Jd#M?~mbC@`5KVTmcw zlwOtnXcUI7;hl+P@u|Tge#vC9$rwokiZ7#p%0UbsL)TU9d8gmuX0LX2avkF6GnvJV zlobQB>Z!-7qDn`TfC*D{o)g!R%#Yd_cwI_UNxK*12k#q{8|!$Csq(fzbD0>v7*vor zm=N^~+5#Lcx2GH45YnIzJ4jP9Z`R)Ld-`Z|zSb+s8@b+tT85cVHI`H)Nb^QvMQAz)J9lJk1Yu?7;znbEtGtx`r z*Bw>mn#&z3kFqbqRi&URa9(}<;F}Ze7AZ*Hd3|2PmT0sPFbiKMHdc2oLlWZZPOOk$ zd2Fd`DuKV5U0RQmqa`FmDvd$~1auodpF%OInIo?92)Rp5by)YLb9*Y)w+xm&wP^xS zCgFlinIjWm8&q%@a2i&`@5F3{&B&m%JabLlvDqxMd%^Y-a8#)}1}2pYUShv6OV;81 z%efL$>c0}MN~$2iV*iQBohe_FkLewOc}Dj2Y?29f!TV@(K3)dYAjY;{?92?o(&3wU zX%IxMXR}dn4bZy5U7dRQ36he91WmU&e2P43l47$xe6oh~>i){cCg}2n&v^Xa{|yZnd>bce=jQ(p}2a^pv7m z{I}nK2M=B+bgXCp?j5uK9X|fotsgyC6Gx~2ojBOY>sSraqxj6$vF{17K?pq6>?JuX zv1VJl&R09gR&TN4|z@r~2_@dsCgZu#DFbno+0r)*y4}kvxVSU*IuqnWU&mqhmlY2&E6M zV>#P4Qx;r~ateA{R?0Ur5H>w_lg<8_EK8AQyBs4qatXC5vuh!Qld~No3|?bEtvv8+ zyZX^N9#;3W*|KGz^rARs5wMYxf)8Bd??wCbD0n<;WExIpNHsdY`=+3z>vJeP!mMXmgR&v?+n0!sM)tSt) z$unkY>}QU+Syq?|TJ8s>igfDKs^3MZo^|Td;wAYW6`zTVScFMbsU9X)qm+C(@O-&* z?(Rp7nR)xFt=u`llinr2j-I~nm)C>kMDt|F)RKk<`R_GWr>d!1YO61v9S7Y0N#U=` z!)yFX7?LkHL%`kCYhy-NNBqV-9h$UX^has}wXs%n+Sqz@|0oV}xEf3^8k##3-M&DD zI5C8|Cze3Hfb)d%%}~YyxVt|9f1K&*=E6SOy|8hyVFjw6_>+(FLSYwSex%KI%WeB_ zV-*ELc!FHMPqEj{PZCfR!NNfLq5sU1h*a0^LLcvy)~4;Tt}RK*1c2o1^>D-d!qAOK z53<8z>Ub6Euwv##gI_UCO#{>OCB;fiBi-xMFhL?;p>c~UK=`Fg)1`;lVY2@g(R$O9 zB8Q_T2>MVLdO4yTK@0GL^D#L(G^=B_le-{=uypqzWA$o@)TaDu}7Xt>xc+ z*C6y4$xh}E?ZotD$Mwa@`Axv@E~l0@e1lO*lS2>cc*vfD{p%iPyeDT+$I!<{T{t8By4NwG)K$6ULTKlif?3%? zdIk8ghwY<6J(5%N*RgH`QeD3=weeFKgZR384zYgCf02)%enQXE4VPDjgvtb z6pV#|{AoBU_QE}QJ!1mGs-&}pk%`|3&Z8;)~bMZ#1~;VAs)mZAZ1_m-VXM z!3BS9R;E*W?Yw;zwOuARCIp7HCHl#wb(%o=~h&#La@-=BDla$l5-! zuyqnO`cEf_B`iUwqAQ3RPlo$Knt-|QK6L)BE9iPJ8@%;^JLv%XAAlZYGvW*bt7t+{ z8JV2@uS2u`Xc4<5iu)oHssyx(8(683k=iJpB?A>EyYTO9aetn|PE}}D^cP+XrNJZy z*jf`wc31c)G3OU5^L)fPxG}ay2IRGN9jbV5i*HCRgBX?bGMD`#jnZ3cG)_ZloQ&@BW=7ApqnNtRuPHfX#F^S$3 z)I!8U!KfYc%2`aTIf~&OV&P>iM^}&IA%|}*Q(&@WjOAeQ?#x8Fj+tyfW}jkx7!n3 zNE-@*v(YB(NI4UrI05@hJ3W|2gcUb!cpI%;KKXFh^nM>su4jKkB6=zVZYFmqe*>fX zCT6gZ&T{|78H1L0_{#oh0G*2#2eGh1wqhDLU170mc=^MkH0ql1U;}>;H=e{-1Yhw>3WPHpdZvbo@qA z1@tLXGQy%>2$V%~nw;(j8e-IK9Uy=#Md(O#)3ignnu{(E?QYpV_kRigiaW$JbI4dK{pwgDnaMsTIE8gR#qZaQ1Z8{NTMx!{(TUH z;>sYEM+_>6wgOreo@7-en|M^a551}T)plr4NV ze~R9Q8e&X%6vZN2_Kc}n0}CV}!5(=FqGCkTsc>ablSvV|N?1iTcj10;9AqKz}j%JOQ&Sb-i}=YcP@%*IigzfG*wJ zSc8jpcXROdaOE;7lvE*0L=vEC?Piszythk2 zR9=MAXcv>5dz2*hQ^3U$?bx7X=GL&?Z=6b*c~(_!YfHOwiT3jYSrmZ-8;EUA6{Co# z7C1-gq+sn}xcrw8^!?&ZWtv9kk(485LB{ooL&S-Rn z$%Wie?tH%&GN-2%{A9F6x2P992c#>zHiT^!4-VUNN<(6zv|7kW5CMktaOMyFit}3s z%=%UEBiBt%)ro32$~tQOe04m@!k&d{S>b)2V%-N_lOXj+PPfMcRR)}>^C7M-`JVM_ zaeY@o_UaGe{yuOfDZ1Ki?YiFj+jH`~pWH{w2`E(o`sgq5Zm-ApDSlz@kf_8UcCtzg z8iU!qMar__n5}&NML^p)5w5~>42v$blZ6XDxx|PRh!xTh-LCOi zPbgQezfG`)zmHK{R5}X0;EcKgCJ4hg-@i-PS7cY?F+ZzSTUfG;g{beN4Zm&#z3Gu> zGc{x8{JB>z!Zz2^^7VhWP`3&cYuR8xGhmA5Xa$K5AH!F&QQZl{=Euf{%k4d4FPI88 zmd;*=kU`M1us)pkArat_+(uEbRP`w3!O<#$#+j|!1$5Nn163rHm4hS_-P`lnZ_ASK zx+3?5OAM0i7WOJ4?D+9OK=p|R>`u$D6*xTlz%i}dJz}YxKG{Azzj)z!q%f+}@LoRL z`H?O3xAAsc{h){ab2Y-VW7=g)LFAb{_?!HTrY{vJnnHaMv1CcPvEMWgn=raYlTwL? z#jv{T<}pQ!G(l|aP8F`^AI?uqFF2?Jp)aefg8}ZsNrE_IS7MazBbheQ!l^?JNGN4z zuf6mZ*`eo|JrvkBy7I;EXlF81EGfa$)@7N*Fo1WJ#R2?Fp_|5gtPfn{i`dKK(W8;5 z^lPDy4fz@s{o(=sMtkdboK5dCO`X$}UjQeU^wa#(l!|K#XA8Zn=71`a$Z-@#Tm?l@ zD5j@bxFAmN<8GNXs7f9)s$KN`V@0k6bLo=B_N=kQV*vOsooBgg6>NUJ<=6}bh*0uC zP!7Z$gw+?VvnU#0{NQOR^l?BLH-r*$8LG^J+t3Xm$h}qRetb(;5jn5S&hjnrZHLL& z4XWwWXKRGK9y_z;Njq>LX+JTbwbUvRw?Ijoe+`irC{SG8;4Yf@3~U=}{Q>G(%_U4p z%ua7*Z|Tq{rk|flpRQP%9OxH6SNH3ID=;rgU1r(#i1>evQ|JY0qgFPLa zKM0OX&w`c32s!uCPJb!T{rHRTeIj2=VEge7H`jzHa~C0J4!|%6iLx`c1fz^pfZJ_i z^+|zW(Lfmb#SQM!!|>RsfrpS6r@HV9`JJVQ9DtGB*CU@e)$E_;Xv>Q*F{NYKJI*ULCHtu-R z0`UJw8;Rn<5{&~RxWi`6+d-5Orwqc2=!O?H0go6B1nMOB*~|&ulJ}fPA7C93Zva9E z0V2V|BV^y-ie+DT1LxrQ`K`nYRj21Hm_lsM>4%lPoD(nRy^K4&?|~v}@^u}5AsSu{ zU&{eXyLH(}kwiXq`G(j>CgoADjjjHuhyI&@Zx>^avV5L<2PoY?)B5b}3#BvX!VD|O zv^0wEcg3Xz&+vl|l5BnkIjg{`G?c6|<{6APwZrjsSri_UNM7l#3FxOvW+R(Pp)5IW z@YcABo|(7}@1tNVY!X-h1(*Kw>W(oNwdqx4q_Gv`{Y+iRwzMqN&$iL&u^f6wIT98K zB$gX?-A-`*j=9_e&%|RJ z2BT}I7Hh6o%xOxm&mKA81kH%RnNkQsg}-J3U(~xj3{`AVDU2qRXjJdD347j1RWvv# zIq7qBjH_j*tU~dvyLw<;1MNrZU_v=t9AieV(-91W5f4%y=XB|*7J-;e1QKO) z_->ajN8F*Y?L&YnQ!&2kdUeOH!MlI+xOe|)*m|Y+A>Vikt;>(wV5$)bP!d>^=_o<7 zkmf+Il-hvtES={$0MZsJ6_wCoxCK2qhCJp%_Sh;36)ZW%({Sx(u-2or3z5{P%cC~Xt$rvc<9-1t*h&;O{CJT`6Za6=~@DCP`Jr9tJ7;z)l$fmp?bxM zKjcf*mlvdKaD=HFgfc2;wy@7(T{sV1x_j?nB@f_HiD5RS(>y>&-%Pm8)=VL~*b#?& z5QfF~<-BQ@KprjV}x@%H?%;6h-1F?YXnnb9p zl~L3R_t8{;XUpPYo`e^kDEjey`?kvfQTzAx_Bmr{dPIA`Z;YVakVqE4cIk=>cr)q_ zJRw;x3K`)L($O~jMO-ci11L@PkoF4hOg&n(uQl&h)?_byK}-xj;~|{de!2vUq>J3E z)+-y1?zG*zPqr?1eqE;dZD)Vo-Hz(NW!>?4gbahT67b9Wa2-DS>=8TOrOuvo{@$*J z!o2SUcrZ+~yL>vzxMCJYuja`>kMe%{2`yR5qmTT1O1;Q&H@;(dvZ}lU%T{{mG3kWDk3kV4P|4~029UN@!jO>jp z%$)xZ6Yhlfzq?jEtN*GVlX9auCpA;Up&qRORC(Mk_%w85(|F!y0hO&uQ>7$MWJ}5C zWB<-)CK-2AzA;+opl3sz*rG4Btxm=aKjmdrO-V2Fzk#zWzwX&A=)S5&XpKM*Wvl}WxwL7qG=uy>75{xUGcDRA zyERIyu)$`7t0L@&2-4ZBrWCH5N!X8z>+4|!W}T^1wZwLn2c^>{>*-wzvh;DcRdX>i z9G3pX-(9KIGMvr2tJ2ViM1ua4a&Jd{4YYc*Fh9%7d7i*d>N;?fwm`H1neo?I2v3$$ zi}HWroZNcX%Bl4y;>ARX`7WJsahzCC?>;JLqOtwvW)uopW%gyy-*&Gb#kSeGZxQU? z(t-kN>uP>F4^IjN`_Fm!mtr`TZ4E_Y-*1^o9VhXho%n05Vpu}5-mmQl6c-m?NBIhK zOK${o`uzbOm-8C{{~1G$+zF+DrHd}RcB}2d=%xJigHGJUgXrHjI`*bp?P=xjRRZ8^ zlRbZH#Qg~!D&9X~`c~$0mUrUy8a5P_Cv1g2E{R{;v?TG^Qv zj*rjc?4Gi}i)9s2a_y0QQ~cxlS_fiB@!*+BUSbzCq*?+HfrDG45Nt?qGS$3=FBNKsWD9D>vHdO!#tj36!xkr|SHF-z;^{XIV}X0<;5;TV1{um8G%pnAQgW9<8b zw#>+IMhW7-Yu%D(P45kcrCIE@rG(Fz96eS7Gz4}*$ATWpvKxPeUfh-*lB1p zq$>&0UXBt=P2W{U&QK$gQB6nlr9q7i%QX{b5j9p2B}mADwIx^G-ms9IzK5Echm9ot zk?5(Wu`;mS%)_b-d#i4myWgL%TtNv{Wm!YIcMqzFgeSAl%mf~!QS*?aU$*T8O9dOy zE?MKU^@s|j{~V4zUW_zmfRm7b(B#_vMGs~FQJ1;bW01<34IHie)AbsEWxA6<2trT% z%Ay)GxtYD(|M+&EqKLP{F{*;HDB{H(EEL4ddwTb;uz{I6m{Oy5QURzZ==L96ONZ?| zAMtPiJ-Dj8l-B%$|A@gqrvgu6M>6w11E%B2Z|yd#S@=O(Yb%HarzB?={5}Rl&62;1 zo$9j0sEZ;R1zM)fc0ij>L!sKd++)=Df#$`F%5|b$rlATTYOAy4G)9IjPC3C-?&5PN zo$8$!Dn{SD(=0Oga32TS1pPJc7{ww^&-vm?;o2V^iCQ#5!-+{^e&Ny;`hzHNI+hz? z*oe7oJjLoyxFz^%^puzJ0=Ur_`+i^PR0mCZ$;@V}kUuZVg!+Q}GKsr43^ zbnDo!*>x<%lysd5H+Pjl^&ISNk%lH@TAR@G$-$FJbh2v@bAA0J@tPbFAFoO#W|H9R z#pt|C6zH~ephD2!CwIE)LoP#e5EB@i9CS&X;|j8emQW8JOHu>qV=M$#oCQ4W4wQ zf%(Jms!a&kRGHMpd#0tl&_#GUfG;uN&MRD7_WB%98j_?? zCBb$(-6KSxP*38c7F5|aNdsd6#aDP)ms4$~3PTqj03DK>)SfN|AfO5GL8;!pFFgDi z!NLv^LbM;s0oK=pJg|;vk-F;i`q{Kv*}1U?^h-vm+pioiNCZO8g5MY>-)LG-3Adu7 zSQAh)!^L98Y>ok|#ld1$%Z?p-n~>w-?&c%&m67;E0?*6HDDWb_MmRLnRfp=_r=~uH zD-pKDQ~Op{?@3gqG!ZMreabR@lX>fpW0 zNG%(lJbo~N`?J9B%?i`PVY4jPCWcpK#|9nJ>aM+;6kfoKK_nz;TH>n~qq1nYH0U4% zIqNtYwx7Y>nL|vUri>P=i(HdTYXHNLE|{f(wF)x_`Ow<#BKEg|<*GSg6PPo^U2P49 zb5OoOMF`kfi4LaW5|6J|%YFdDiX|b|bfx_?aDRVO*)WGUuhW$9^m>b!hQg<(wUUW7 zTm?e#pAw{jIEg5cigJ}>u(?AcYK|UzQuG({$PM;>G9v>g?9P?WsCkbTYJpG?$kr>> zy-AdEwHham8EMeDvkbmm^2xfO0LYQ6^1MEa<^8NMMb+PmD1Dwt&viTW0lXJ$NDw~m z17a)Q5wpTaS)TIymXu8PeOmGyZnH5lpu;8drA8FaJL&2STVN|GL(1sL%5&<48tFi` zFga_?kuQydb9MBL?;g77p1_x3wcj6)B=uw#(k+91@4R(qtpuPH`n%!#*zd-Qzn_WL zcIC4h^WDcSPtp3+98jA_OH-kfxjDXb?1oI{-*i$yFEU;uo1vK-%2k%fs&#S}N7}bs z&ZG^so5AxwX>(6!;Uw{*)?cvZidE9Zp3FZF$F{cbKCj1VB#uKQ# z|Jday+{MVIS6BR9ki{O(ba{O)^|#LT$72t?5BljN;5FP(ie<0F@_Cbp7Z&;|?W2Ee zjoWM69WiYMu!Jqji{2bj=a$Z5(4}O*d1Ec-qaiUh8KB1->&8LjEBu4Pz;QLx`&OiL zzLlCH4Tm(9Jevf;SA$jeC(5We*(#6P4K0+G5d)Cq<1%`FNKj@ycm7}|z%4)tA@Vg6 zGCqNY=OBowfjF$4b$crN&#AyKQ8oviWT2>EFw-Mcx8+4 z3mPbzoO&W?Ql5;~ivJpL&OnTfd`yg&DptB+f2^2pK#=HaiwMB-qK{l{*K*z#nASBL< zpb1s+JEoG3)&pD2_99pGNjD-Ra{Bx3{l`fR0T|IZki_Z|MpNd#--O)=XmRK%8tGc7 zaC3DNyRMm-@e1aQ?kjtXa#(l28KF0w4Hva>q1Ct%g(ZZ&``I;zs!$Ic6ZollmO7+p zx+l_84gv%}VRb3&S38#0Oe_ywLpW-`Vj@cvs>&^5l0$rjlyMr*`aI{?X$lqh2cIhJ zK|aK2mRiJ(pn?Kj7Ny2dtN?t&aW4ZRO4Wl7-Z2=0eCf8_=av&!!qY?CBXKz|o&f#i z0pB1^@afhX?|5r%Uq+;YC$`$T^ln7x*>D?)~QRWFUv)Yn!ajD*2 z^n2uH%1!G~l&DKfHFOP|{z+hvg8!;0y7uD?B_2y8@|$&xQh{C7DPL>}Pkl8|?iP1c z;v<{VE?9A-agHVO4}hE{*H}e*8Ned=+Arco1DNSX}{-$Z-dV zsO$4Sxw@ujuXQHW=qe94%RFsjm!HDJdjd0SV)w&>L9e@D%CO${0pT7M$Sfo)gbqsj}wpgi~4MGeR#Q`j|()0fNvSFt1Lh}etC~⋙s&A5-lDZ;nWRUF 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%0A~Vb@$VGg{O%q=9g_b7fCC&Ygo0@wA|yR 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≤T|LBMzkeo#5z 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 datetime object in UTC + + # Current time in UTC + now = datetime.now(timezone.utc) + + # Calculate latency + latency = now - message_time + return latency + +@serverless_function +def lambda_handler(event, context): + try: + records = event["Records"] + record_count = len(records) + print(f"Received {record_count} records in this invocation.") + + for record in records: + raw_body = record.get('body') + if not raw_body: + raise ValueError("SQS message has no body.") + + # Parse the body (SNS wrapper around your actual message) + body = json.loads(raw_body) + print(f"Processing message body: {body}") + + latency = process_latency(body) + + raw_message = body.get("Message") + if not raw_message: + raise ValueError("Message field is missing or empty.") + + # Parse the actual application message + message = json.loads(raw_message) + print(f"Processing message: {message}") + + headers = message.get("headers") or {} + + with sentry_trace(headers, raw_body, body, latency) as span: + inventory_list = message.get("inventory") + if not isinstance(inventory_list, list): + raise ValueError("Inventory field is missing or not a list.") + + for idx, quantity in enumerate(inventory_list): + if quantity < 0: + # print(f"throw syntax error { "headers" }") + raise ValueError( + f"Negative inventory detected for index {idx}: {quantity}" + ) + + return {"statusCode": 200, "body": f"Processed {record_count} record(s)"} + + except Exception as e: + # Capture the exception in Sentry + sentry_sdk.capture_exception(e) + return {"statusCode": 500, "body": str(e)} + + diff --git a/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/INSTALLER b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/INSTALLER new file mode 100644 index 000000000..a1b589e38 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/LICENSE b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/LICENSE new file mode 100644 index 000000000..c4c8162f1 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-2024 Functional Software, Inc. dba Sentry + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/METADATA b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/METADATA new file mode 100644 index 000000000..b7302b38e --- /dev/null +++ b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/METADATA @@ -0,0 +1,244 @@ +Metadata-Version: 2.2 +Name: sentry-sdk +Version: 2.20.0 +Summary: Python client for Sentry (https://sentry.io) +Home-page: https://github.com/getsentry/sentry-python +Author: Sentry Team and Contributors +Author-email: hello@sentry.io +License: MIT +Project-URL: Documentation, https://docs.sentry.io/platforms/python/ +Project-URL: Changelog, https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.6 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: urllib3>=1.26.11 +Requires-Dist: certifi +Provides-Extra: aiohttp +Requires-Dist: aiohttp>=3.5; extra == "aiohttp" +Provides-Extra: anthropic +Requires-Dist: anthropic>=0.16; extra == "anthropic" +Provides-Extra: arq +Requires-Dist: arq>=0.23; extra == "arq" +Provides-Extra: asyncpg +Requires-Dist: asyncpg>=0.23; extra == "asyncpg" +Provides-Extra: beam +Requires-Dist: apache-beam>=2.12; extra == "beam" +Provides-Extra: bottle +Requires-Dist: bottle>=0.12.13; extra == "bottle" +Provides-Extra: celery +Requires-Dist: celery>=3; extra == "celery" +Provides-Extra: celery-redbeat +Requires-Dist: celery-redbeat>=2; extra == "celery-redbeat" +Provides-Extra: chalice +Requires-Dist: chalice>=1.16.0; extra == "chalice" +Provides-Extra: clickhouse-driver +Requires-Dist: clickhouse-driver>=0.2.0; extra == "clickhouse-driver" +Provides-Extra: django +Requires-Dist: django>=1.8; extra == "django" +Provides-Extra: falcon +Requires-Dist: falcon>=1.4; extra == "falcon" +Provides-Extra: fastapi +Requires-Dist: fastapi>=0.79.0; extra == "fastapi" +Provides-Extra: flask +Requires-Dist: flask>=0.11; extra == "flask" +Requires-Dist: blinker>=1.1; extra == "flask" +Requires-Dist: markupsafe; extra == "flask" +Provides-Extra: grpcio +Requires-Dist: grpcio>=1.21.1; extra == "grpcio" +Requires-Dist: protobuf>=3.8.0; extra == "grpcio" +Provides-Extra: http2 +Requires-Dist: httpcore[http2]==1.*; extra == "http2" +Provides-Extra: httpx +Requires-Dist: httpx>=0.16.0; extra == "httpx" +Provides-Extra: huey +Requires-Dist: huey>=2; extra == "huey" +Provides-Extra: huggingface-hub +Requires-Dist: huggingface_hub>=0.22; extra == "huggingface-hub" +Provides-Extra: langchain +Requires-Dist: langchain>=0.0.210; extra == "langchain" +Provides-Extra: launchdarkly +Requires-Dist: launchdarkly-server-sdk>=9.8.0; extra == "launchdarkly" +Provides-Extra: litestar +Requires-Dist: litestar>=2.0.0; extra == "litestar" +Provides-Extra: loguru +Requires-Dist: loguru>=0.5; extra == "loguru" +Provides-Extra: openai +Requires-Dist: openai>=1.0.0; extra == "openai" +Requires-Dist: tiktoken>=0.3.0; extra == "openai" +Provides-Extra: openfeature +Requires-Dist: openfeature-sdk>=0.7.1; extra == "openfeature" +Provides-Extra: opentelemetry +Requires-Dist: opentelemetry-distro>=0.35b0; extra == "opentelemetry" +Provides-Extra: opentelemetry-experimental +Requires-Dist: opentelemetry-distro; extra == "opentelemetry-experimental" +Provides-Extra: pure-eval +Requires-Dist: pure_eval; extra == "pure-eval" +Requires-Dist: executing; extra == "pure-eval" +Requires-Dist: asttokens; extra == "pure-eval" +Provides-Extra: pymongo +Requires-Dist: pymongo>=3.1; extra == "pymongo" +Provides-Extra: pyspark +Requires-Dist: pyspark>=2.4.4; extra == "pyspark" +Provides-Extra: quart +Requires-Dist: quart>=0.16.1; extra == "quart" +Requires-Dist: blinker>=1.1; extra == "quart" +Provides-Extra: rq +Requires-Dist: rq>=0.6; extra == "rq" +Provides-Extra: sanic +Requires-Dist: sanic>=0.8; extra == "sanic" +Provides-Extra: sqlalchemy +Requires-Dist: sqlalchemy>=1.2; extra == "sqlalchemy" +Provides-Extra: starlette +Requires-Dist: starlette>=0.19.1; extra == "starlette" +Provides-Extra: starlite +Requires-Dist: starlite>=1.48; extra == "starlite" +Provides-Extra: tornado +Requires-Dist: tornado>=6; extra == "tornado" +Provides-Extra: unleash +Requires-Dist: UnleashClient>=6.0.1; extra == "unleash" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: description-content-type +Dynamic: home-page +Dynamic: license +Dynamic: project-url +Dynamic: provides-extra +Dynamic: requires-dist +Dynamic: requires-python +Dynamic: summary + +
    + Sentry for Python + + + +_Bad software is everywhere, and we're tired of it. Sentry is on a mission to help developers write better software faster, so we can get back to enjoying technology. If you want to join us, [**check out our open positions**](https://sentry.io/careers/)_. + +# Official Sentry SDK for Python + +[![Build Status](https://github.com/getsentry/sentry-python/actions/workflows/ci.yml/badge.svg)](https://github.com/getsentry/sentry-python/actions/workflows/ci.yml) +[![PyPi page link -- version](https://img.shields.io/pypi/v/sentry-sdk.svg)](https://pypi.python.org/pypi/sentry-sdk) +[![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/cWnMQeA) + +Welcome to the official Python SDK for **[Sentry](http://sentry.io/)**! + +## Getting Started + +### Installation + +Getting Sentry into your project is straightforward. Just run this command in your terminal: + +```bash +pip install --upgrade sentry-sdk +``` + +### Basic Configuration + +Here’s a quick configuration example to get Sentry up and running: + +```python +import sentry_sdk + +sentry_sdk.init( + "https://12927b5f211046b575ee51fd8b1ac34f@o1.ingest.sentry.io/1", # Your DSN here + + # Set traces_sample_rate to 1.0 to capture 100% + # of transactions for performance monitoring. + traces_sample_rate=1.0, +) +``` + +With this configuration, Sentry will monitor for exceptions and performance issues. + +### Quick Usage Example + +To generate some events that will show up in Sentry, you can log messages or capture errors: + +```python +from sentry_sdk import capture_message +capture_message("Hello Sentry!") # You'll see this in your Sentry dashboard. + +raise ValueError("Oops, something went wrong!") # This will create an error event in Sentry. +``` + +#### Explore the Docs + +For more details on advanced usage, integrations, and customization, check out the full documentation: + +- [Official SDK Docs](https://docs.sentry.io/platforms/python/) +- [API Reference](https://getsentry.github.io/sentry-python/) + +## Integrations + +Sentry integrates with many popular Python libraries and frameworks, including: + +- [Django](https://docs.sentry.io/platforms/python/integrations/django/) +- [Flask](https://docs.sentry.io/platforms/python/integrations/flask/) +- [FastAPI](https://docs.sentry.io/platforms/python/integrations/fastapi/) +- [Celery](https://docs.sentry.io/platforms/python/integrations/celery/) +- [AWS Lambda](https://docs.sentry.io/platforms/python/integrations/aws-lambda/) + +Want more? [Check out the full list of integrations](https://docs.sentry.io/platforms/python/integrations/). + +### Rolling Your Own Integration? + +If you want to create a new integration or improve an existing one, we’d welcome your contributions! Please read our [contributing guide](https://github.com/getsentry/sentry-python/blob/master/CONTRIBUTING.md) before starting. + +## Migrating Between Versions? + +### From `1.x` to `2.x` + +If you're using the older `1.x` version of the SDK, now's the time to upgrade to `2.x`. It includes significant upgrades and new features. Check our [migration guide](https://docs.sentry.io/platforms/python/migration/1.x-to-2.x) for assistance. + +### From `raven-python` + +Using the legacy `raven-python` client? It's now in maintenance mode, and we recommend migrating to the new SDK for an improved experience. Get all the details in our [migration guide](https://docs.sentry.io/platforms/python/migration/raven-to-sentry-sdk/). + +## Want to Contribute? + +We’d love your help in improving the Sentry SDK! Whether it’s fixing bugs, adding features, or enhancing documentation, every contribution is valuable. + +For details on how to contribute, please check out [CONTRIBUTING.md](CONTRIBUTING.md) and explore the [open issues](https://github.com/getsentry/sentry-python/issues). + +## Need Help? + +If you encounter issues or need help setting up or configuring the SDK, don’t hesitate to reach out to the [Sentry Community on Discord](https://discord.com/invite/Ww9hbqr). There is a ton of great people there ready to help! + +## Resources + +Here are additional resources to help you make the most of Sentry: + +- [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/quickstart/) – Official documentation to get started. +- [![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/Ww9hbqr) – Join our Discord community. +- [![Twitter Follow](https://img.shields.io/twitter/follow/getsentry?label=getsentry&style=social)](https://twitter.com/intent/follow?screen_name=getsentry) – Follow us on X (Twitter) for updates. +- [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](http://stackoverflow.com/questions/tagged/sentry) – Questions and answers related to Sentry. + +## License + +The SDK is open-source and available under the MIT license. Check out the [LICENSE](LICENSE) file for more information. + +--- + +Thanks to everyone who has helped improve the SDK! + + + + diff --git a/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/RECORD b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/RECORD new file mode 100644 index 000000000..40eb13171 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/RECORD @@ -0,0 +1,293 @@ +sentry_sdk-2.20.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +sentry_sdk-2.20.0.dist-info/LICENSE,sha256=bX7GLcIOi5LLiLifs7Ve6FdlBfPhr6V5UlSPbupdAI4,1098 +sentry_sdk-2.20.0.dist-info/METADATA,sha256=5jTZM3opL-B5iPQFXyooNocWEK7RMMGjjaDmIaMB1U4,10191 +sentry_sdk-2.20.0.dist-info/RECORD,, +sentry_sdk-2.20.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +sentry_sdk-2.20.0.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109 +sentry_sdk-2.20.0.dist-info/entry_points.txt,sha256=qacZEz40UspQZD1IukCXykx0JtImqGDOctS5KfOLTko,91 +sentry_sdk-2.20.0.dist-info/top_level.txt,sha256=XrQz30XE9FKXSY_yGLrd9bsv2Rk390GTDJOSujYaMxI,11 +sentry_sdk/__init__.py,sha256=ywM5WQA3Qy4500dumhgHDSNWwVmMikmOIdhIvmAaTMg,1179 +sentry_sdk/__pycache__/__init__.cpython-311.pyc,, +sentry_sdk/__pycache__/_compat.cpython-311.pyc,, +sentry_sdk/__pycache__/_init_implementation.cpython-311.pyc,, +sentry_sdk/__pycache__/_lru_cache.cpython-311.pyc,, +sentry_sdk/__pycache__/_queue.cpython-311.pyc,, +sentry_sdk/__pycache__/_types.cpython-311.pyc,, +sentry_sdk/__pycache__/_werkzeug.cpython-311.pyc,, +sentry_sdk/__pycache__/api.cpython-311.pyc,, +sentry_sdk/__pycache__/attachments.cpython-311.pyc,, +sentry_sdk/__pycache__/client.cpython-311.pyc,, +sentry_sdk/__pycache__/consts.cpython-311.pyc,, +sentry_sdk/__pycache__/debug.cpython-311.pyc,, +sentry_sdk/__pycache__/envelope.cpython-311.pyc,, +sentry_sdk/__pycache__/feature_flags.cpython-311.pyc,, +sentry_sdk/__pycache__/hub.cpython-311.pyc,, +sentry_sdk/__pycache__/metrics.cpython-311.pyc,, +sentry_sdk/__pycache__/monitor.cpython-311.pyc,, +sentry_sdk/__pycache__/scope.cpython-311.pyc,, +sentry_sdk/__pycache__/scrubber.cpython-311.pyc,, +sentry_sdk/__pycache__/serializer.cpython-311.pyc,, +sentry_sdk/__pycache__/session.cpython-311.pyc,, +sentry_sdk/__pycache__/sessions.cpython-311.pyc,, +sentry_sdk/__pycache__/spotlight.cpython-311.pyc,, +sentry_sdk/__pycache__/tracing.cpython-311.pyc,, +sentry_sdk/__pycache__/tracing_utils.cpython-311.pyc,, +sentry_sdk/__pycache__/transport.cpython-311.pyc,, +sentry_sdk/__pycache__/types.cpython-311.pyc,, +sentry_sdk/__pycache__/utils.cpython-311.pyc,, +sentry_sdk/__pycache__/worker.cpython-311.pyc,, +sentry_sdk/_compat.py,sha256=Pxcg6cUYPiOoXIFfLI_H3ATb7SfrcXOeZdzpeWv3umI,3116 +sentry_sdk/_init_implementation.py,sha256=WL54d8nggjRunBm3XlG-sWSx4yS5lpYYggd7YBWpuVk,2559 +sentry_sdk/_lru_cache.py,sha256=phZMBm9EKU1m67OOApnKCffnlWAlVz9bYjig7CglQuk,1229 +sentry_sdk/_queue.py,sha256=8oUHpMgSzS40rxfHmjRYlKAfvKtrPvK1FL56RbnJ1iY,11248 +sentry_sdk/_types.py,sha256=iTcUMwzxiIJ8u038tyYDK_WpvDsAA3h5QoUONqsMc8M,6806 +sentry_sdk/_werkzeug.py,sha256=m3GPf-jHd8v3eVOfBHaKw5f0uHoLkXrSO1EcY-8EisY,3734 +sentry_sdk/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +sentry_sdk/ai/__pycache__/__init__.cpython-311.pyc,, +sentry_sdk/ai/__pycache__/monitoring.cpython-311.pyc,, +sentry_sdk/ai/__pycache__/utils.cpython-311.pyc,, +sentry_sdk/ai/monitoring.py,sha256=eUW7j7AFG4sKuTIiAVJFnQ747roJJkVr_uyRla9hinE,4446 +sentry_sdk/ai/utils.py,sha256=QCwhHoptrdXyYroJqzCKxqi0cmrlD9IDDWUcBk6yWZc,950 +sentry_sdk/api.py,sha256=dYwjjLVtFuUT5VYUvc0xuSWZ7wFS9GYpZp6wRD2CX4M,11298 +sentry_sdk/attachments.py,sha256=0Dylhm065O6hNFjB40fWCd5Hg4qWSXndmi1TPWglZkI,3109 +sentry_sdk/client.py,sha256=WS54LXZxBOsYt0vEIFaI6umMfpKi7kJwt5OlCo9hK3c,32784 +sentry_sdk/consts.py,sha256=NrOjk8mEUDcjnmrcfsENnbaUHVJrRWqLPDO7PyCEhAw,18757 +sentry_sdk/crons/__init__.py,sha256=3Zt6g1-pZZ12uRKKsC8QLm3XgJ4K1VYxgVpNNUygOZY,221 +sentry_sdk/crons/__pycache__/__init__.cpython-311.pyc,, +sentry_sdk/crons/__pycache__/api.cpython-311.pyc,, +sentry_sdk/crons/__pycache__/consts.cpython-311.pyc,, +sentry_sdk/crons/__pycache__/decorator.cpython-311.pyc,, +sentry_sdk/crons/api.py,sha256=s3x6SG-jqIdWS-Kj0sAxJv0nz2A3stdGE1UCtQyRUy4,1559 +sentry_sdk/crons/consts.py,sha256=dXqJk5meBSu5rjlGpqAOlkpACnuUi7svQnAFoy1ZNUU,87 +sentry_sdk/crons/decorator.py,sha256=UrjeIqBCbvsuKrfjGkKJbbLBvjw2TQvDWcTO7WwAmrI,3913 +sentry_sdk/debug.py,sha256=ddBehQlAuQC1sg1XO-N4N3diZ0x0iT5RWJwFdrtcsjw,1019 +sentry_sdk/envelope.py,sha256=wN3vs-BTDRn5LeYYMSmNotxOF8DVtpQo3OLuJ8NAzT0,10178 +sentry_sdk/feature_flags.py,sha256=MLTfvHXeqH0dPAGUboKLPCjuzrU-G5_phXwo72ZCl0k,1075 +sentry_sdk/hub.py,sha256=2QLvEtIYSYV04r8h7VBmQjookILaiBZxZBGTtQKNAWg,25675 +sentry_sdk/integrations/__init__.py,sha256=NC6ioNIiv5cd302he8oWa_ImD6Q-iPdm6pCfbKWhwCc,9736 +sentry_sdk/integrations/__pycache__/__init__.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/_asgi_common.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/_wsgi_common.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/aiohttp.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/anthropic.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/argv.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/ariadne.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/arq.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/asgi.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/asyncio.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/asyncpg.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/atexit.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/aws_lambda.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/beam.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/boto3.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/bottle.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/chalice.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/clickhouse_driver.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/cloud_resource_context.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/cohere.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/dedupe.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/dramatiq.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/excepthook.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/executing.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/falcon.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/fastapi.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/flask.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/gcp.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/gnu_backtrace.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/gql.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/graphene.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/httpx.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/huey.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/huggingface_hub.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/langchain.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/launchdarkly.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/litestar.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/logging.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/loguru.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/modules.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/openai.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/openfeature.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/pure_eval.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/pymongo.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/pyramid.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/quart.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/ray.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/rq.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/rust_tracing.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/sanic.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/serverless.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/socket.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/sqlalchemy.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/starlette.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/starlite.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/stdlib.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/strawberry.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/sys_exit.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/threading.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/tornado.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/trytond.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/typer.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/unleash.cpython-311.pyc,, +sentry_sdk/integrations/__pycache__/wsgi.cpython-311.pyc,, +sentry_sdk/integrations/_asgi_common.py,sha256=Ypg7IctB3iPPY60ebVlzChzgT8GeGpZ0YH8VvJNDlEY,3187 +sentry_sdk/integrations/_wsgi_common.py,sha256=6qgplAbnCCClrOa5gLYvug6c6CgelyAxOY_6DvClzt8,7422 +sentry_sdk/integrations/aiohttp.py,sha256=_7o5lqmxfkT4RqN0VM84tYi5DtwmuYFaxkW5QiY2rDU,12903 +sentry_sdk/integrations/anthropic.py,sha256=Lbww6CilrrP2x5lPB4LLX_OYmhbZgwaEMFekWtrO6SE,9413 +sentry_sdk/integrations/argv.py,sha256=GIY7TBFETF8Z0fDzqTXEJldt5XXCDdFNZxpGxP7EPaU,911 +sentry_sdk/integrations/ariadne.py,sha256=e5WWdoTQCzIz0p10cvcwSnVIRO21kiXFA2XwkkJHDfM,5850 +sentry_sdk/integrations/arq.py,sha256=yPpbmM5gfV1MRPDS_G7-FGkxSBZrUvGXkni7B-f1olY,7812 +sentry_sdk/integrations/asgi.py,sha256=vA5tE9eN4pFambuPj1EVO7wmVAMJuVcSX-of5XqKEoM,12688 +sentry_sdk/integrations/asyncio.py,sha256=nAjrQzGb1v9oM5I8d6FEN6XOU7yBhyu_8v-n87ztLX4,3199 +sentry_sdk/integrations/asyncpg.py,sha256=fbBTi5bEERK3c9o43LBLtS5wPaSVq_qIl3Y50NPmr5Y,6521 +sentry_sdk/integrations/atexit.py,sha256=sY46N2hEvtGuT1DBQhirUXHbjgXjXAm7R_sgiectVKw,1652 +sentry_sdk/integrations/aws_lambda.py,sha256=AoJCM-EHXjvOvm3EI26TM8Cm_uhkWc1FwX1KAbnJNAY,17834 +sentry_sdk/integrations/beam.py,sha256=qt35UmkA0ng4VNzmwqH9oz7SESU-is9IjFbTJ21ad4U,5182 +sentry_sdk/integrations/boto3.py,sha256=1ItKUX7EL9MHXS1H8VSn6IfZSFLeqaUqeWg-OKBm_Ik,4411 +sentry_sdk/integrations/bottle.py,sha256=9b2PiwW-oQmgTFegcC2XitUlEBnbkkPMS2dTsfb1GRU,6473 +sentry_sdk/integrations/celery/__init__.py,sha256=EXjaXxIk2SCqD3Q7S5BYv_0B7KMPh-HQ95lEY9obIdk,18650 +sentry_sdk/integrations/celery/__pycache__/__init__.cpython-311.pyc,, +sentry_sdk/integrations/celery/__pycache__/beat.cpython-311.pyc,, +sentry_sdk/integrations/celery/__pycache__/utils.cpython-311.pyc,, +sentry_sdk/integrations/celery/beat.py,sha256=wIRqiY8lsi-PwAnMwsqVDoAbkMlGDacOE5_xaYgRj_Q,8947 +sentry_sdk/integrations/celery/utils.py,sha256=CMWQOpg9yniEkm3WlXe7YakJfVnLwaY0-jyeo2GX-ZI,1208 +sentry_sdk/integrations/chalice.py,sha256=q3uWOA91JGQCTNG9m0EyGyoB1Z9aWQkdPRu3TPOXr3A,4711 +sentry_sdk/integrations/clickhouse_driver.py,sha256=-CN3MLtiOy3ryqjh2sSD-TUI_gvhG2DRrvKgoWszd3w,5247 +sentry_sdk/integrations/cloud_resource_context.py,sha256=pswdnRDnm_jeFprQ_qM56AIVnEK1ZKVj7tpIzlKNgWY,6744 +sentry_sdk/integrations/cohere.py,sha256=H2z6b92fMKvbqP4OMePWwOEHVXBxXneTdt02XbjDZI0,9266 +sentry_sdk/integrations/dedupe.py,sha256=VczYIzHmpm9xfMwZG4c-xGYWON7T4DqByDtyRkFIuPs,1171 +sentry_sdk/integrations/django/__init__.py,sha256=e67rJ3b1dRM9gGvfe2EHWgN47nmlvumkPDIGVh7FWkY,24977 +sentry_sdk/integrations/django/__pycache__/__init__.cpython-311.pyc,, +sentry_sdk/integrations/django/__pycache__/asgi.cpython-311.pyc,, +sentry_sdk/integrations/django/__pycache__/caching.cpython-311.pyc,, +sentry_sdk/integrations/django/__pycache__/middleware.cpython-311.pyc,, +sentry_sdk/integrations/django/__pycache__/signals_handlers.cpython-311.pyc,, +sentry_sdk/integrations/django/__pycache__/templates.cpython-311.pyc,, +sentry_sdk/integrations/django/__pycache__/transactions.cpython-311.pyc,, +sentry_sdk/integrations/django/__pycache__/views.cpython-311.pyc,, +sentry_sdk/integrations/django/asgi.py,sha256=FNqxqR5JI7ugPu_GhUvcII-eGaT6vLXHb62RazqeT58,8301 +sentry_sdk/integrations/django/caching.py,sha256=UvYaiI7xrN08Se59vMgJWrSO2BuowOyx3jmXmZoxQJo,6427 +sentry_sdk/integrations/django/middleware.py,sha256=UVKq134w_TyOVPV7WwBW0QjHY-ziDipcZBIDQmjqceE,6009 +sentry_sdk/integrations/django/signals_handlers.py,sha256=iudWetTlzNr5-kx_ew1YwW_vZ0yDChoonwPZB7AYGPo,3098 +sentry_sdk/integrations/django/templates.py,sha256=k3PQrNICGS4wqmFxK3o8KwOlqip7rSIryyc4oa1Wexc,5725 +sentry_sdk/integrations/django/transactions.py,sha256=Axyh3l4UvM96R3go2anVhew3JbrEZ4FSYd1r3UXEcw4,4951 +sentry_sdk/integrations/django/views.py,sha256=bjHwt6TVfYY7yfGUa2Rat9yowkUbQ2bYCcJaXJxP2Ik,3137 +sentry_sdk/integrations/dramatiq.py,sha256=q2v9qTQvveyW6OVjviPosM6REk2fl1PglxTqxTaw_c4,5575 +sentry_sdk/integrations/excepthook.py,sha256=tfwpSQuo1b_OmJbNKPPRh90EUjD_OSE4DqqgYY9PVQI,2408 +sentry_sdk/integrations/executing.py,sha256=5lxBAxO5FypY-zTV03AHncGmolmaHd327-3Vrjzskcc,1994 +sentry_sdk/integrations/falcon.py,sha256=uhjqFPKa8bWRQr0za4pVXGYaPr-LRdICw2rUO-laKCo,9501 +sentry_sdk/integrations/fastapi.py,sha256=E4Uj-aby7TebpIUClLXoIwnjgJ6zLFcOSdl_dP0zj9Q,4726 +sentry_sdk/integrations/flask.py,sha256=-XsAwGWxqxwn9WBT-os2J3HOnMqVWrPttNOEP59kveo,8289 +sentry_sdk/integrations/gcp.py,sha256=0HwjtHUFLNzBITzSM3jpPgom8J5dY6udmzayZL4muJs,8286 +sentry_sdk/integrations/gnu_backtrace.py,sha256=cVY7t6gjVjeRf4PdnmZrATFqMOZ7-qJu-84xIXOD5R4,2894 +sentry_sdk/integrations/gql.py,sha256=eEdeuj9zfSei8n0LM2KZTH4E2m4y4q3O_Dnwl5o79r0,4172 +sentry_sdk/integrations/graphene.py,sha256=uKLHwHq4leV2bJGpek6teoPjHSFHqMzDIUTRC5TFoxI,5074 +sentry_sdk/integrations/grpc/__init__.py,sha256=HIrP4x4rfDrf-JW2IpJezOVo_MZ-Iy-E6Q2ZWwi4o0w,4950 +sentry_sdk/integrations/grpc/__pycache__/__init__.cpython-311.pyc,, +sentry_sdk/integrations/grpc/__pycache__/client.cpython-311.pyc,, +sentry_sdk/integrations/grpc/__pycache__/consts.cpython-311.pyc,, +sentry_sdk/integrations/grpc/__pycache__/server.cpython-311.pyc,, +sentry_sdk/integrations/grpc/aio/__init__.py,sha256=2rgrliowpPfLLw40_2YU6ixSzIu_3f8NN3TRplzc8S8,141 +sentry_sdk/integrations/grpc/aio/__pycache__/__init__.cpython-311.pyc,, +sentry_sdk/integrations/grpc/aio/__pycache__/client.cpython-311.pyc,, +sentry_sdk/integrations/grpc/aio/__pycache__/server.cpython-311.pyc,, +sentry_sdk/integrations/grpc/aio/client.py,sha256=csOwlJb7fg9fBnzeNHxr-qpZEmU97I_jnqkCq6ZLFAs,3322 +sentry_sdk/integrations/grpc/aio/server.py,sha256=M-Xx5yczfAmwb3RH0gBGVsKbGghWVVfjseSq1YYEaLY,4028 +sentry_sdk/integrations/grpc/client.py,sha256=rOPwbU0IO6Ve99atvvwhdVZA8nqBy7_wbH2frb0kIJ0,3382 +sentry_sdk/integrations/grpc/consts.py,sha256=NpsN5gKWDmtGtVK_L5HscgFZBHqjOpmLJLGKyh8GZBA,31 +sentry_sdk/integrations/grpc/server.py,sha256=4Z_66z4Ejujbp5Wv_BrmmRsV2HW8VVTMZ1Rie_ft1bY,2483 +sentry_sdk/integrations/httpx.py,sha256=WwUulqzBLoGGqWUUdQg_MThwQUKzBXnA-m3g_1GOpCE,5866 +sentry_sdk/integrations/huey.py,sha256=sdLLitNJS13AkdZWy60fECl5TY1q9OJcpMV_W1mvfSM,5450 +sentry_sdk/integrations/huggingface_hub.py,sha256=A6uUwGmoGCis5yyb1W55-nSCiylSIKa8v0OvoIES0YI,6537 +sentry_sdk/integrations/langchain.py,sha256=_k34XP9H-5S-mDyF2tiJd-CjiiTDUWKZsmxsfJH5wzQ,17718 +sentry_sdk/integrations/launchdarkly.py,sha256=XukgF62Ft6FfL9hhnIV5pZalW3oaYpjGfYCeP2FzcHI,1948 +sentry_sdk/integrations/litestar.py,sha256=mnTRJuR8zNxCysKEYzR5yodADkvpSCol_Utpi_6GmBg,10932 +sentry_sdk/integrations/logging.py,sha256=ifj0Ex0975alGPCiKq-ExVSuKkPm8UeOjEWpKMLiTk8,9597 +sentry_sdk/integrations/loguru.py,sha256=Gzs2ACyMFQZWe7lscsAKbuCVCSSy1OpTPhBrkes2qfA,3001 +sentry_sdk/integrations/modules.py,sha256=vzLx3Erg77Vl4mnUvAgTg_3teAuWy7zylFpAidBI9I0,820 +sentry_sdk/integrations/openai.py,sha256=UI_-Y1NPbCIi0AG2q6ME44vhSDx0SuAXleaB4v_X23Q,15557 +sentry_sdk/integrations/openfeature.py,sha256=R34K8Yb4CJNkxN5Otq9Sq1C9Ng4zPc3XFKpPHot5lCo,1299 +sentry_sdk/integrations/opentelemetry/__init__.py,sha256=emNL5aAq_NhK0PZmfX_g4GIdvBS6nHqGrjrIgrdC5m8,229 +sentry_sdk/integrations/opentelemetry/__pycache__/__init__.cpython-311.pyc,, +sentry_sdk/integrations/opentelemetry/__pycache__/consts.cpython-311.pyc,, +sentry_sdk/integrations/opentelemetry/__pycache__/integration.cpython-311.pyc,, +sentry_sdk/integrations/opentelemetry/__pycache__/propagator.cpython-311.pyc,, +sentry_sdk/integrations/opentelemetry/__pycache__/span_processor.cpython-311.pyc,, +sentry_sdk/integrations/opentelemetry/consts.py,sha256=fYL6FIAEfnGZGBhFn5X7aRyHxihSPqAKKqMLhf5Gniw,143 +sentry_sdk/integrations/opentelemetry/integration.py,sha256=CWp6hFFMqoR7wcuwTRbRO-1iVch4A6oOB3RuHWeX9GQ,1791 +sentry_sdk/integrations/opentelemetry/propagator.py,sha256=NpCgv2Ibq1LUrv8-URayZaPGSzz0f1tJsf7aaxAZ5pc,3720 +sentry_sdk/integrations/opentelemetry/span_processor.py,sha256=IBF75ld9zJLNF1-4EYnNBoAS00_XTXjPio86zPX9DLQ,13276 +sentry_sdk/integrations/pure_eval.py,sha256=OvT76XvllQ_J6ABu3jVNU6KD2QAxnXMtTZ7hqhXNhpY,4581 +sentry_sdk/integrations/pymongo.py,sha256=cPpMGEbXHlV6HTHgmIDL1F-x3w7ZMROXVb4eUhLs3bw,6380 +sentry_sdk/integrations/pyramid.py,sha256=IDonzoZvLrH18JL-i_Qpbztc4T3iZNQhWFFv6SAXac8,7364 +sentry_sdk/integrations/quart.py,sha256=pPFB-MVYGj_nfmZK9BRKxJHiqmBVulUnW0nAxI7FDOc,7437 +sentry_sdk/integrations/ray.py,sha256=oTVw0dY1iLpAbU22n0Eh_A1cCunAT3Zdz6JwO3NVXD0,4169 +sentry_sdk/integrations/redis/__init__.py,sha256=As5XhbOue-9Sy9d8Vr8cZagbO_Bc0uG8n2G3YNMP7TU,1332 +sentry_sdk/integrations/redis/__pycache__/__init__.cpython-311.pyc,, +sentry_sdk/integrations/redis/__pycache__/_async_common.cpython-311.pyc,, +sentry_sdk/integrations/redis/__pycache__/_sync_common.cpython-311.pyc,, +sentry_sdk/integrations/redis/__pycache__/consts.cpython-311.pyc,, +sentry_sdk/integrations/redis/__pycache__/rb.cpython-311.pyc,, +sentry_sdk/integrations/redis/__pycache__/redis.cpython-311.pyc,, +sentry_sdk/integrations/redis/__pycache__/redis_cluster.cpython-311.pyc,, +sentry_sdk/integrations/redis/__pycache__/redis_py_cluster_legacy.cpython-311.pyc,, +sentry_sdk/integrations/redis/__pycache__/utils.cpython-311.pyc,, +sentry_sdk/integrations/redis/_async_common.py,sha256=Ay-0XOzDaiFD4pNjq_hO8wU8w2K-ZajFVrypuCYCN5E,3791 +sentry_sdk/integrations/redis/_sync_common.py,sha256=FxWQaPPHNIRcBRBv3unV-vB9Zvs75PdoUmDOaJcYTqk,3581 +sentry_sdk/integrations/redis/consts.py,sha256=jYhloX935YQ1AR9c8giCVo1FpIuGXkGR_Tfn4LOulNU,480 +sentry_sdk/integrations/redis/modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +sentry_sdk/integrations/redis/modules/__pycache__/__init__.cpython-311.pyc,, +sentry_sdk/integrations/redis/modules/__pycache__/caches.cpython-311.pyc,, +sentry_sdk/integrations/redis/modules/__pycache__/queries.cpython-311.pyc,, +sentry_sdk/integrations/redis/modules/caches.py,sha256=eY8XY4Nk3QsMM0T26OOYdcNr4bN0Sp9325HkH-hO8cg,4063 +sentry_sdk/integrations/redis/modules/queries.py,sha256=0GxZ98wyjqcc4CwPG3xJ4bSGIGW8wPXChSk5Fxm6kYg,2035 +sentry_sdk/integrations/redis/rb.py,sha256=paykO7EE_DAdiZzCpIqW1MqtBE7mE5UG0JnauFejuzE,806 +sentry_sdk/integrations/redis/redis.py,sha256=1K6seuP6ttEdscKLFtEYEu9vkDRuANCsxWVeDISsGsg,1702 +sentry_sdk/integrations/redis/redis_cluster.py,sha256=D-b_wnX4sgxW4qxJP2kKe8ArJRvEtqrLQNYyStl5D6s,3333 +sentry_sdk/integrations/redis/redis_py_cluster_legacy.py,sha256=pz5pg0AxdHPZWt0jMQRDPH_9jdh0i3KoDPbNUyavIro,1585 +sentry_sdk/integrations/redis/utils.py,sha256=EeUdhTU6rTsNUtqRW5kWZTWYF8Ct1wTvIRKXI6y63-8,3956 +sentry_sdk/integrations/rq.py,sha256=wnDRYgymk9EPqprFwTmS4us0X-G1Ba6_9eQy2REuHXo,5314 +sentry_sdk/integrations/rust_tracing.py,sha256=fQ0eG09w3IPZe8ecgeUoQTPoGFThkkarUyGC8KJj35o,9078 +sentry_sdk/integrations/sanic.py,sha256=D-9AOoJArYZ83x8Nx1Xt6od2TFpED_ewjjHeQB0mhu8,12999 +sentry_sdk/integrations/serverless.py,sha256=npiKJuIy_sEkWT_x0Eu2xSEMiMh_aySqGYlnvIROsYk,1804 +sentry_sdk/integrations/socket.py,sha256=UqY4MPZ77x9hAi4vMoXV1AkdZNpVGFqRxP8F3O25Gsw,3036 +sentry_sdk/integrations/spark/__init__.py,sha256=oOewMErnZk2rzNvIlZO6URxQexu9bUJuSLM2m_zECy8,208 +sentry_sdk/integrations/spark/__pycache__/__init__.cpython-311.pyc,, +sentry_sdk/integrations/spark/__pycache__/spark_driver.cpython-311.pyc,, +sentry_sdk/integrations/spark/__pycache__/spark_worker.cpython-311.pyc,, +sentry_sdk/integrations/spark/spark_driver.py,sha256=RbD4ORofv4m11aSlY5--9jnDUjBptSAyecVvGTiPTMU,8913 +sentry_sdk/integrations/spark/spark_worker.py,sha256=FGT4yRU2X_iQCC46aasMmvJfYOKmBip8KbDF_wnhvEY,3706 +sentry_sdk/integrations/sqlalchemy.py,sha256=QemZA6BmmZN5A8ux84gYdelJ9G9G-6kZQB7a5yRJCtQ,4372 +sentry_sdk/integrations/starlette.py,sha256=SA9HHZYt5zzL1w4NDO7AQq_kpJHNYodbc6ANK91kKqg,26158 +sentry_sdk/integrations/starlite.py,sha256=0Wk0rv4U9RR5Z_woDlLK9fvI98PJAC6jyv81oh3DhP8,10628 +sentry_sdk/integrations/stdlib.py,sha256=vgB9weDGh455vBwmUSgcQRgzViKstu3O0syOthCn_H0,8831 +sentry_sdk/integrations/strawberry.py,sha256=EdDsMYkHbiNzIVFWete20ukmFHYKdbm1rW-NGYeLiM0,15416 +sentry_sdk/integrations/sys_exit.py,sha256=AwShgGBWPdiY25aOWDLRAs2RBUKm5T3CrL-Q-zAk0l4,2493 +sentry_sdk/integrations/threading.py,sha256=AAQuxDfp9_HuL_1vAwzwSRftsCLsttuQdNiqU1569JU,4011 +sentry_sdk/integrations/tornado.py,sha256=AWKqzWzxGTKwo70oK2c9uAVnFW-jo54hTWpqWpKpVLE,7258 +sentry_sdk/integrations/trytond.py,sha256=BaLCNqQeRWDbHHDEelS5tmj-p_CrbmtGEHIn6JfzEFE,1651 +sentry_sdk/integrations/typer.py,sha256=FQrFgpR9t6yQWF-oWCI9KJLFioEnA2c_1BEtYV-mPAs,1815 +sentry_sdk/integrations/unleash.py,sha256=b6T_mY18V84NwgFEPBNJSMeqqbpkc-rTL_j0JsCgAoI,1075 +sentry_sdk/integrations/wsgi.py,sha256=nv8DMawFPLUSoF74Sm-Wo3K6E6p2djEAPH3jWP_bVC8,10755 +sentry_sdk/metrics.py,sha256=-hfIxEOLcRo2KG1qSuD5F2IgSGdx9eAHGH65oJWCjZ4,30025 +sentry_sdk/monitor.py,sha256=7LydPMKjVRR5eFY9rxgvJv0idExA3sSnrZk-1mHu6G4,3710 +sentry_sdk/profiler/__init__.py,sha256=b3z-s_lUtah4G9rBL38Od5CSs5RbAOCXzgSVkhUCJDo,1063 +sentry_sdk/profiler/__pycache__/__init__.cpython-311.pyc,, +sentry_sdk/profiler/__pycache__/continuous_profiler.cpython-311.pyc,, +sentry_sdk/profiler/__pycache__/transaction_profiler.cpython-311.pyc,, +sentry_sdk/profiler/__pycache__/utils.cpython-311.pyc,, +sentry_sdk/profiler/continuous_profiler.py,sha256=Y_rXVs1v2S1eCxzQZqZb7hp9af5B3SfvMNmrgu_MGxc,17155 +sentry_sdk/profiler/transaction_profiler.py,sha256=ZRqfytls9bVu8OECniyLQwOu2ha_PbOKl7Gy50ymSl0,27876 +sentry_sdk/profiler/utils.py,sha256=G5s4tYai9ATJqcHrQ3bOIxlK6jIaHzELrDtU5k3N4HI,6556 +sentry_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +sentry_sdk/scope.py,sha256=iB76Supu-_gHn_on44P2Gi5txH92sH-IQ3Lg60qYulU,60434 +sentry_sdk/scrubber.py,sha256=F3yIqTD-V6KdPFYsNBqbgmOvd50RZthUi0TluvMJQ8o,5926 +sentry_sdk/serializer.py,sha256=iXiRwTuRj0gcKyHRO0GNTZB1Hmk0LMDiBt6Be7RpGt8,13087 +sentry_sdk/session.py,sha256=TqDVmRKKHUDSmZb4jQR-s8wDt7Fwb6QaG21hawUGWEs,5571 +sentry_sdk/sessions.py,sha256=DKgZh4xq-ccOmTqzX98fp-dZn0b6WwbLCbfMOp8x27o,9181 +sentry_sdk/spotlight.py,sha256=J95p0F4lA5NC7QjGOBeNYWd2LD4ySy-SkTUSvnTe7wg,8149 +sentry_sdk/tracing.py,sha256=9dNPHCa3dAtYhwFelqyRICU4QQOap5LXRajtIorGM1Y,45879 +sentry_sdk/tracing_utils.py,sha256=AIjE_xVLrHevglKkk5Fr5a6jtiHt5-0Nmpbjn61y2BQ,22386 +sentry_sdk/transport.py,sha256=KAtbh4R45lPyFRP5FAEg2Orf-sLXaZ7IG_IGKY6ClRU,32184 +sentry_sdk/types.py,sha256=S4sOblXMzr39F3BpEECp5M4FsA7uSd5mWiKRFSyeZpY,800 +sentry_sdk/utils.py,sha256=J3ydgt9pNW6YQfFjlLIqk0kAbcL9lFypv5_IsiyrkfM,59792 +sentry_sdk/worker.py,sha256=VSMaigRMbInVyupSFpBC42bft2oIViea-0C_d9ThnIo,4464 diff --git a/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/REQUESTED b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/REQUESTED new file mode 100644 index 000000000..e69de29bb diff --git a/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/WHEEL b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/WHEEL new file mode 100644 index 000000000..eaea6f3b5 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: setuptools (75.8.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/entry_points.txt b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/entry_points.txt new file mode 100644 index 000000000..4b128a843 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[opentelemetry_propagator] +sentry = sentry_sdk.integrations.opentelemetry:SentryPropagator diff --git a/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/top_level.txt b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/top_level.txt new file mode 100644 index 000000000..5051901ec --- /dev/null +++ b/aws/lambda_demo/sentry_sdk-2.20.0.dist-info/top_level.txt @@ -0,0 +1 @@ +sentry_sdk diff --git a/aws/lambda_demo/sentry_sdk/__init__.py b/aws/lambda_demo/sentry_sdk/__init__.py new file mode 100644 index 000000000..1c9cedec5 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/__init__.py @@ -0,0 +1,57 @@ +from sentry_sdk.scope import Scope +from sentry_sdk.transport import Transport, HttpTransport +from sentry_sdk.client import Client + +from sentry_sdk.api import * # noqa + +from sentry_sdk.consts import VERSION # noqa + +__all__ = [ # noqa + "Hub", + "Scope", + "Client", + "Transport", + "HttpTransport", + "integrations", + # From sentry_sdk.api + "init", + "add_breadcrumb", + "capture_event", + "capture_exception", + "capture_message", + "configure_scope", + "continue_trace", + "flush", + "get_baggage", + "get_client", + "get_global_scope", + "get_isolation_scope", + "get_current_scope", + "get_current_span", + "get_traceparent", + "is_initialized", + "isolation_scope", + "last_event_id", + "new_scope", + "push_scope", + "set_context", + "set_extra", + "set_level", + "set_measurement", + "set_tag", + "set_tags", + "set_user", + "start_span", + "start_transaction", + "trace", + "monitor", +] + +# Initialize the debug support after everything is loaded +from sentry_sdk.debug import init_debug_support + +init_debug_support() +del init_debug_support + +# circular imports +from sentry_sdk.hub import Hub diff --git a/aws/lambda_demo/sentry_sdk/_compat.py b/aws/lambda_demo/sentry_sdk/_compat.py new file mode 100644 index 000000000..a811cf212 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/_compat.py @@ -0,0 +1,98 @@ +import sys + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import TypeVar + + T = TypeVar("T") + + +PY37 = sys.version_info[0] == 3 and sys.version_info[1] >= 7 +PY38 = sys.version_info[0] == 3 and sys.version_info[1] >= 8 +PY310 = sys.version_info[0] == 3 and sys.version_info[1] >= 10 +PY311 = sys.version_info[0] == 3 and sys.version_info[1] >= 11 + + +def with_metaclass(meta, *bases): + # type: (Any, *Any) -> Any + class MetaClass(type): + def __new__(metacls, name, this_bases, d): + # type: (Any, Any, Any, Any) -> Any + return meta(name, bases, d) + + return type.__new__(MetaClass, "temporary_class", (), {}) + + +def check_uwsgi_thread_support(): + # type: () -> bool + # We check two things here: + # + # 1. uWSGI doesn't run in threaded mode by default -- issue a warning if + # that's the case. + # + # 2. Additionally, if uWSGI is running in preforking mode (default), it needs + # the --py-call-uwsgi-fork-hooks option for the SDK to work properly. This + # is because any background threads spawned before the main process is + # forked are NOT CLEANED UP IN THE CHILDREN BY DEFAULT even if + # --enable-threads is on. One has to explicitly provide + # --py-call-uwsgi-fork-hooks to force uWSGI to run regular cpython + # after-fork hooks that take care of cleaning up stale thread data. + try: + from uwsgi import opt # type: ignore + except ImportError: + return True + + from sentry_sdk.consts import FALSE_VALUES + + def enabled(option): + # type: (str) -> bool + value = opt.get(option, False) + if isinstance(value, bool): + return value + + if isinstance(value, bytes): + try: + value = value.decode() + except Exception: + pass + + return value and str(value).lower() not in FALSE_VALUES + + # When `threads` is passed in as a uwsgi option, + # `enable-threads` is implied on. + threads_enabled = "threads" in opt or enabled("enable-threads") + fork_hooks_on = enabled("py-call-uwsgi-fork-hooks") + lazy_mode = enabled("lazy-apps") or enabled("lazy") + + if lazy_mode and not threads_enabled: + from warnings import warn + + warn( + Warning( + "IMPORTANT: " + "We detected the use of uWSGI without thread support. " + "This might lead to unexpected issues. " + 'Please run uWSGI with "--enable-threads" for full support.' + ) + ) + + return False + + elif not lazy_mode and (not threads_enabled or not fork_hooks_on): + from warnings import warn + + warn( + Warning( + "IMPORTANT: " + "We detected the use of uWSGI in preforking mode without " + "thread support. This might lead to crashing workers. " + 'Please run uWSGI with both "--enable-threads" and ' + '"--py-call-uwsgi-fork-hooks" for full support.' + ) + ) + + return False + + return True diff --git a/aws/lambda_demo/sentry_sdk/_init_implementation.py b/aws/lambda_demo/sentry_sdk/_init_implementation.py new file mode 100644 index 000000000..eb02b3d11 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/_init_implementation.py @@ -0,0 +1,84 @@ +import warnings + +from typing import TYPE_CHECKING + +import sentry_sdk + +if TYPE_CHECKING: + from typing import Any, ContextManager, Optional + + import sentry_sdk.consts + + +class _InitGuard: + _CONTEXT_MANAGER_DEPRECATION_WARNING_MESSAGE = ( + "Using the return value of sentry_sdk.init as a context manager " + "and manually calling the __enter__ and __exit__ methods on the " + "return value are deprecated. We are no longer maintaining this " + "functionality, and we will remove it in the next major release." + ) + + def __init__(self, client): + # type: (sentry_sdk.Client) -> None + self._client = client + + def __enter__(self): + # type: () -> _InitGuard + warnings.warn( + self._CONTEXT_MANAGER_DEPRECATION_WARNING_MESSAGE, + stacklevel=2, + category=DeprecationWarning, + ) + + return self + + def __exit__(self, exc_type, exc_value, tb): + # type: (Any, Any, Any) -> None + warnings.warn( + self._CONTEXT_MANAGER_DEPRECATION_WARNING_MESSAGE, + stacklevel=2, + category=DeprecationWarning, + ) + + c = self._client + if c is not None: + c.close() + + +def _check_python_deprecations(): + # type: () -> None + # Since we're likely to deprecate Python versions in the future, I'm keeping + # this handy function around. Use this to detect the Python version used and + # to output logger.warning()s if it's deprecated. + pass + + +def _init(*args, **kwargs): + # type: (*Optional[str], **Any) -> ContextManager[Any] + """Initializes the SDK and optionally integrations. + + This takes the same arguments as the client constructor. + """ + client = sentry_sdk.Client(*args, **kwargs) + sentry_sdk.get_global_scope().set_client(client) + _check_python_deprecations() + rv = _InitGuard(client) + return rv + + +if TYPE_CHECKING: + # Make mypy, PyCharm and other static analyzers think `init` is a type to + # have nicer autocompletion for params. + # + # Use `ClientConstructor` to define the argument types of `init` and + # `ContextManager[Any]` to tell static analyzers about the return type. + + class init(sentry_sdk.consts.ClientConstructor, _InitGuard): # noqa: N801 + pass + +else: + # Alias `init` for actual usage. Go through the lambda indirection to throw + # PyCharm off of the weakly typed signature (it would otherwise discover + # both the weakly typed signature of `_init` and our faked `init` type). + + init = (lambda: _init)() diff --git a/aws/lambda_demo/sentry_sdk/_lru_cache.py b/aws/lambda_demo/sentry_sdk/_lru_cache.py new file mode 100644 index 000000000..cbadd9723 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/_lru_cache.py @@ -0,0 +1,47 @@ +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + + +_SENTINEL = object() + + +class LRUCache: + def __init__(self, max_size): + # type: (int) -> None + if max_size <= 0: + raise AssertionError(f"invalid max_size: {max_size}") + self.max_size = max_size + self._data = {} # type: dict[Any, Any] + self.hits = self.misses = 0 + self.full = False + + def set(self, key, value): + # type: (Any, Any) -> None + current = self._data.pop(key, _SENTINEL) + if current is not _SENTINEL: + self._data[key] = value + elif self.full: + self._data.pop(next(iter(self._data))) + self._data[key] = value + else: + self._data[key] = value + self.full = len(self._data) >= self.max_size + + def get(self, key, default=None): + # type: (Any, Any) -> Any + try: + ret = self._data.pop(key) + except KeyError: + self.misses += 1 + ret = default + else: + self.hits += 1 + self._data[key] = ret + + return ret + + def get_all(self): + # type: () -> list[tuple[Any, Any]] + return list(self._data.items()) diff --git a/aws/lambda_demo/sentry_sdk/_queue.py b/aws/lambda_demo/sentry_sdk/_queue.py new file mode 100644 index 000000000..c0410d1f9 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/_queue.py @@ -0,0 +1,287 @@ +""" +A fork of Python 3.6's stdlib queue (found in Pythons 'cpython/Lib/queue.py') +with Lock swapped out for RLock to avoid a deadlock while garbage collecting. + +https://github.com/python/cpython/blob/v3.6.12/Lib/queue.py + + +See also +https://codewithoutrules.com/2017/08/16/concurrency-python/ +https://bugs.python.org/issue14976 +https://github.com/sqlalchemy/sqlalchemy/blob/4eb747b61f0c1b1c25bdee3856d7195d10a0c227/lib/sqlalchemy/queue.py#L1 + +We also vendor the code to evade eventlet's broken monkeypatching, see +https://github.com/getsentry/sentry-python/pull/484 + + +Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation; + +All Rights Reserved + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + +""" + +import threading + +from collections import deque +from time import time + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + +__all__ = ["EmptyError", "FullError", "Queue"] + + +class EmptyError(Exception): + "Exception raised by Queue.get(block=0)/get_nowait()." + pass + + +class FullError(Exception): + "Exception raised by Queue.put(block=0)/put_nowait()." + pass + + +class Queue: + """Create a queue object with a given maximum size. + + If maxsize is <= 0, the queue size is infinite. + """ + + def __init__(self, maxsize=0): + self.maxsize = maxsize + self._init(maxsize) + + # mutex must be held whenever the queue is mutating. All methods + # that acquire mutex must release it before returning. mutex + # is shared between the three conditions, so acquiring and + # releasing the conditions also acquires and releases mutex. + self.mutex = threading.RLock() + + # Notify not_empty whenever an item is added to the queue; a + # thread waiting to get is notified then. + self.not_empty = threading.Condition(self.mutex) + + # Notify not_full whenever an item is removed from the queue; + # a thread waiting to put is notified then. + self.not_full = threading.Condition(self.mutex) + + # Notify all_tasks_done whenever the number of unfinished tasks + # drops to zero; thread waiting to join() is notified to resume + self.all_tasks_done = threading.Condition(self.mutex) + self.unfinished_tasks = 0 + + def task_done(self): + """Indicate that a formerly enqueued task is complete. + + Used by Queue consumer threads. For each get() used to fetch a task, + a subsequent call to task_done() tells the queue that the processing + on the task is complete. + + If a join() is currently blocking, it will resume when all items + have been processed (meaning that a task_done() call was received + for every item that had been put() into the queue). + + Raises a ValueError if called more times than there were items + placed in the queue. + """ + with self.all_tasks_done: + unfinished = self.unfinished_tasks - 1 + if unfinished <= 0: + if unfinished < 0: + raise ValueError("task_done() called too many times") + self.all_tasks_done.notify_all() + self.unfinished_tasks = unfinished + + def join(self): + """Blocks until all items in the Queue have been gotten and processed. + + The count of unfinished tasks goes up whenever an item is added to the + queue. The count goes down whenever a consumer thread calls task_done() + to indicate the item was retrieved and all work on it is complete. + + When the count of unfinished tasks drops to zero, join() unblocks. + """ + with self.all_tasks_done: + while self.unfinished_tasks: + self.all_tasks_done.wait() + + def qsize(self): + """Return the approximate size of the queue (not reliable!).""" + with self.mutex: + return self._qsize() + + def empty(self): + """Return True if the queue is empty, False otherwise (not reliable!). + + This method is likely to be removed at some point. Use qsize() == 0 + as a direct substitute, but be aware that either approach risks a race + condition where a queue can grow before the result of empty() or + qsize() can be used. + + To create code that needs to wait for all queued tasks to be + completed, the preferred technique is to use the join() method. + """ + with self.mutex: + return not self._qsize() + + def full(self): + """Return True if the queue is full, False otherwise (not reliable!). + + This method is likely to be removed at some point. Use qsize() >= n + as a direct substitute, but be aware that either approach risks a race + condition where a queue can shrink before the result of full() or + qsize() can be used. + """ + with self.mutex: + return 0 < self.maxsize <= self._qsize() + + def put(self, item, block=True, timeout=None): + """Put an item into the queue. + + If optional args 'block' is true and 'timeout' is None (the default), + block if necessary until a free slot is available. If 'timeout' is + a non-negative number, it blocks at most 'timeout' seconds and raises + the FullError exception if no free slot was available within that time. + Otherwise ('block' is false), put an item on the queue if a free slot + is immediately available, else raise the FullError exception ('timeout' + is ignored in that case). + """ + with self.not_full: + if self.maxsize > 0: + if not block: + if self._qsize() >= self.maxsize: + raise FullError() + elif timeout is None: + while self._qsize() >= self.maxsize: + self.not_full.wait() + elif timeout < 0: + raise ValueError("'timeout' must be a non-negative number") + else: + endtime = time() + timeout + while self._qsize() >= self.maxsize: + remaining = endtime - time() + if remaining <= 0.0: + raise FullError() + self.not_full.wait(remaining) + self._put(item) + self.unfinished_tasks += 1 + self.not_empty.notify() + + def get(self, block=True, timeout=None): + """Remove and return an item from the queue. + + If optional args 'block' is true and 'timeout' is None (the default), + block if necessary until an item is available. If 'timeout' is + a non-negative number, it blocks at most 'timeout' seconds and raises + the EmptyError exception if no item was available within that time. + Otherwise ('block' is false), return an item if one is immediately + available, else raise the EmptyError exception ('timeout' is ignored + in that case). + """ + with self.not_empty: + if not block: + if not self._qsize(): + raise EmptyError() + elif timeout is None: + while not self._qsize(): + self.not_empty.wait() + elif timeout < 0: + raise ValueError("'timeout' must be a non-negative number") + else: + endtime = time() + timeout + while not self._qsize(): + remaining = endtime - time() + if remaining <= 0.0: + raise EmptyError() + self.not_empty.wait(remaining) + item = self._get() + self.not_full.notify() + return item + + def put_nowait(self, item): + """Put an item into the queue without blocking. + + Only enqueue the item if a free slot is immediately available. + Otherwise raise the FullError exception. + """ + return self.put(item, block=False) + + def get_nowait(self): + """Remove and return an item from the queue without blocking. + + Only get an item if one is immediately available. Otherwise + raise the EmptyError exception. + """ + return self.get(block=False) + + # Override these methods to implement other queue organizations + # (e.g. stack or priority queue). + # These will only be called with appropriate locks held + + # Initialize the queue representation + def _init(self, maxsize): + self.queue = deque() # type: Any + + def _qsize(self): + return len(self.queue) + + # Put a new item in the queue + def _put(self, item): + self.queue.append(item) + + # Get an item from the queue + def _get(self): + return self.queue.popleft() diff --git a/aws/lambda_demo/sentry_sdk/_types.py b/aws/lambda_demo/sentry_sdk/_types.py new file mode 100644 index 000000000..4e3c195cc --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/_types.py @@ -0,0 +1,222 @@ +from typing import TYPE_CHECKING + + +# Re-exported for compat, since code out there in the wild might use this variable. +MYPY = TYPE_CHECKING + + +if TYPE_CHECKING: + from collections.abc import Container, MutableMapping, Sequence + + from datetime import datetime + + from types import TracebackType + from typing import Any + from typing import Callable + from typing import Dict + from typing import Mapping + from typing import NotRequired + from typing import Optional + from typing import Tuple + from typing import Type + from typing import Union + from typing_extensions import Literal, TypedDict + + class SDKInfo(TypedDict): + name: str + version: str + packages: Sequence[Mapping[str, str]] + + # "critical" is an alias of "fatal" recognized by Relay + LogLevelStr = Literal["fatal", "critical", "error", "warning", "info", "debug"] + + DurationUnit = Literal[ + "nanosecond", + "microsecond", + "millisecond", + "second", + "minute", + "hour", + "day", + "week", + ] + + InformationUnit = Literal[ + "bit", + "byte", + "kilobyte", + "kibibyte", + "megabyte", + "mebibyte", + "gigabyte", + "gibibyte", + "terabyte", + "tebibyte", + "petabyte", + "pebibyte", + "exabyte", + "exbibyte", + ] + + FractionUnit = Literal["ratio", "percent"] + MeasurementUnit = Union[DurationUnit, InformationUnit, FractionUnit, str] + + MeasurementValue = TypedDict( + "MeasurementValue", + { + "value": float, + "unit": NotRequired[Optional[MeasurementUnit]], + }, + ) + + Event = TypedDict( + "Event", + { + "breadcrumbs": dict[ + Literal["values"], list[dict[str, Any]] + ], # TODO: We can expand on this type + "check_in_id": str, + "contexts": dict[str, dict[str, object]], + "dist": str, + "duration": Optional[float], + "environment": str, + "errors": list[dict[str, Any]], # TODO: We can expand on this type + "event_id": str, + "exception": dict[ + Literal["values"], list[dict[str, Any]] + ], # TODO: We can expand on this type + "extra": MutableMapping[str, object], + "fingerprint": list[str], + "level": LogLevelStr, + "logentry": Mapping[str, object], + "logger": str, + "measurements": dict[str, MeasurementValue], + "message": str, + "modules": dict[str, str], + "monitor_config": Mapping[str, object], + "monitor_slug": Optional[str], + "platform": Literal["python"], + "profile": object, # Should be sentry_sdk.profiler.Profile, but we can't import that here due to circular imports + "release": str, + "request": dict[str, object], + "sdk": Mapping[str, object], + "server_name": str, + "spans": list[dict[str, object]], + "stacktrace": dict[ + str, object + ], # We access this key in the code, but I am unsure whether we ever set it + "start_timestamp": datetime, + "status": Optional[str], + "tags": MutableMapping[ + str, str + ], # Tags must be less than 200 characters each + "threads": dict[ + Literal["values"], list[dict[str, Any]] + ], # TODO: We can expand on this type + "timestamp": Optional[datetime], # Must be set before sending the event + "transaction": str, + "transaction_info": Mapping[str, Any], # TODO: We can expand on this type + "type": Literal["check_in", "transaction"], + "user": dict[str, object], + "_metrics_summary": dict[str, object], + }, + total=False, + ) + + ExcInfo = Union[ + tuple[Type[BaseException], BaseException, Optional[TracebackType]], + tuple[None, None, None], + ] + + Hint = Dict[str, Any] + + Breadcrumb = Dict[str, Any] + BreadcrumbHint = Dict[str, Any] + + SamplingContext = Dict[str, Any] + + EventProcessor = Callable[[Event, Hint], Optional[Event]] + ErrorProcessor = Callable[[Event, ExcInfo], Optional[Event]] + BreadcrumbProcessor = Callable[[Breadcrumb, BreadcrumbHint], Optional[Breadcrumb]] + TransactionProcessor = Callable[[Event, Hint], Optional[Event]] + + TracesSampler = Callable[[SamplingContext], Union[float, int, bool]] + + # https://github.com/python/mypy/issues/5710 + NotImplementedType = Any + + EventDataCategory = Literal[ + "default", + "error", + "crash", + "transaction", + "security", + "attachment", + "session", + "internal", + "profile", + "profile_chunk", + "metric_bucket", + "monitor", + "span", + ] + SessionStatus = Literal["ok", "exited", "crashed", "abnormal"] + + ContinuousProfilerMode = Literal["thread", "gevent", "unknown"] + ProfilerMode = Union[ContinuousProfilerMode, Literal["sleep"]] + + # Type of the metric. + MetricType = Literal["d", "s", "g", "c"] + + # Value of the metric. + MetricValue = Union[int, float, str] + + # Internal representation of tags as a tuple of tuples (this is done in order to allow for the same key to exist + # multiple times). + MetricTagsInternal = Tuple[Tuple[str, str], ...] + + # External representation of tags as a dictionary. + MetricTagValue = Union[str, int, float, None] + MetricTags = Mapping[str, MetricTagValue] + + # Value inside the generator for the metric value. + FlushedMetricValue = Union[int, float] + + BucketKey = Tuple[MetricType, str, MeasurementUnit, MetricTagsInternal] + MetricMetaKey = Tuple[MetricType, str, MeasurementUnit] + + MonitorConfigScheduleType = Literal["crontab", "interval"] + MonitorConfigScheduleUnit = Literal[ + "year", + "month", + "week", + "day", + "hour", + "minute", + "second", # not supported in Sentry and will result in a warning + ] + + MonitorConfigSchedule = TypedDict( + "MonitorConfigSchedule", + { + "type": MonitorConfigScheduleType, + "value": Union[int, str], + "unit": MonitorConfigScheduleUnit, + }, + total=False, + ) + + MonitorConfig = TypedDict( + "MonitorConfig", + { + "schedule": MonitorConfigSchedule, + "timezone": str, + "checkin_margin": int, + "max_runtime": int, + "failure_issue_threshold": int, + "recovery_threshold": int, + }, + total=False, + ) + + HttpStatusCodeRange = Union[int, Container[int]] diff --git a/aws/lambda_demo/sentry_sdk/_werkzeug.py b/aws/lambda_demo/sentry_sdk/_werkzeug.py new file mode 100644 index 000000000..0fa3d611f --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/_werkzeug.py @@ -0,0 +1,98 @@ +""" +Copyright (c) 2007 by the Pallets team. + +Some rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. +""" + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Dict + from typing import Iterator + from typing import Tuple + + +# +# `get_headers` comes from `werkzeug.datastructures.EnvironHeaders` +# https://github.com/pallets/werkzeug/blob/0.14.1/werkzeug/datastructures.py#L1361 +# +# We need this function because Django does not give us a "pure" http header +# dict. So we might as well use it for all WSGI integrations. +# +def _get_headers(environ): + # type: (Dict[str, str]) -> Iterator[Tuple[str, str]] + """ + Returns only proper HTTP headers. + """ + for key, value in environ.items(): + key = str(key) + if key.startswith("HTTP_") and key not in ( + "HTTP_CONTENT_TYPE", + "HTTP_CONTENT_LENGTH", + ): + yield key[5:].replace("_", "-").title(), value + elif key in ("CONTENT_TYPE", "CONTENT_LENGTH"): + yield key.replace("_", "-").title(), value + + +# +# `get_host` comes from `werkzeug.wsgi.get_host` +# https://github.com/pallets/werkzeug/blob/1.0.1/src/werkzeug/wsgi.py#L145 +# +def get_host(environ, use_x_forwarded_for=False): + # type: (Dict[str, str], bool) -> str + """ + Return the host for the given WSGI environment. + """ + if use_x_forwarded_for and "HTTP_X_FORWARDED_HOST" in environ: + rv = environ["HTTP_X_FORWARDED_HOST"] + if environ["wsgi.url_scheme"] == "http" and rv.endswith(":80"): + rv = rv[:-3] + elif environ["wsgi.url_scheme"] == "https" and rv.endswith(":443"): + rv = rv[:-4] + elif environ.get("HTTP_HOST"): + rv = environ["HTTP_HOST"] + if environ["wsgi.url_scheme"] == "http" and rv.endswith(":80"): + rv = rv[:-3] + elif environ["wsgi.url_scheme"] == "https" and rv.endswith(":443"): + rv = rv[:-4] + elif environ.get("SERVER_NAME"): + rv = environ["SERVER_NAME"] + if (environ["wsgi.url_scheme"], environ["SERVER_PORT"]) not in ( + ("https", "443"), + ("http", "80"), + ): + rv += ":" + environ["SERVER_PORT"] + else: + # In spite of the WSGI spec, SERVER_NAME might not be present. + rv = "unknown" + + return rv diff --git a/aws/lambda_demo/sentry_sdk/ai/__init__.py b/aws/lambda_demo/sentry_sdk/ai/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/aws/lambda_demo/sentry_sdk/ai/monitoring.py b/aws/lambda_demo/sentry_sdk/ai/monitoring.py new file mode 100644 index 000000000..860833b8f --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/ai/monitoring.py @@ -0,0 +1,115 @@ +import inspect +from functools import wraps + +import sentry_sdk.utils +from sentry_sdk import start_span +from sentry_sdk.tracing import Span +from sentry_sdk.utils import ContextVar + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Optional, Callable, Any + +_ai_pipeline_name = ContextVar("ai_pipeline_name", default=None) + + +def set_ai_pipeline_name(name): + # type: (Optional[str]) -> None + _ai_pipeline_name.set(name) + + +def get_ai_pipeline_name(): + # type: () -> Optional[str] + return _ai_pipeline_name.get() + + +def ai_track(description, **span_kwargs): + # type: (str, Any) -> Callable[..., Any] + def decorator(f): + # type: (Callable[..., Any]) -> Callable[..., Any] + def sync_wrapped(*args, **kwargs): + # type: (Any, Any) -> Any + curr_pipeline = _ai_pipeline_name.get() + op = span_kwargs.get("op", "ai.run" if curr_pipeline else "ai.pipeline") + + with start_span(name=description, op=op, **span_kwargs) as span: + for k, v in kwargs.pop("sentry_tags", {}).items(): + span.set_tag(k, v) + for k, v in kwargs.pop("sentry_data", {}).items(): + span.set_data(k, v) + if curr_pipeline: + span.set_data("ai.pipeline.name", curr_pipeline) + return f(*args, **kwargs) + else: + _ai_pipeline_name.set(description) + try: + res = f(*args, **kwargs) + except Exception as e: + event, hint = sentry_sdk.utils.event_from_exception( + e, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "ai_monitoring", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + raise e from None + finally: + _ai_pipeline_name.set(None) + return res + + async def async_wrapped(*args, **kwargs): + # type: (Any, Any) -> Any + curr_pipeline = _ai_pipeline_name.get() + op = span_kwargs.get("op", "ai.run" if curr_pipeline else "ai.pipeline") + + with start_span(name=description, op=op, **span_kwargs) as span: + for k, v in kwargs.pop("sentry_tags", {}).items(): + span.set_tag(k, v) + for k, v in kwargs.pop("sentry_data", {}).items(): + span.set_data(k, v) + if curr_pipeline: + span.set_data("ai.pipeline.name", curr_pipeline) + return await f(*args, **kwargs) + else: + _ai_pipeline_name.set(description) + try: + res = await f(*args, **kwargs) + except Exception as e: + event, hint = sentry_sdk.utils.event_from_exception( + e, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "ai_monitoring", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + raise e from None + finally: + _ai_pipeline_name.set(None) + return res + + if inspect.iscoroutinefunction(f): + return wraps(f)(async_wrapped) + else: + return wraps(f)(sync_wrapped) + + return decorator + + +def record_token_usage( + span, prompt_tokens=None, completion_tokens=None, total_tokens=None +): + # type: (Span, Optional[int], Optional[int], Optional[int]) -> None + ai_pipeline_name = get_ai_pipeline_name() + if ai_pipeline_name: + span.set_data("ai.pipeline.name", ai_pipeline_name) + if prompt_tokens is not None: + span.set_measurement("ai_prompt_tokens_used", value=prompt_tokens) + if completion_tokens is not None: + span.set_measurement("ai_completion_tokens_used", value=completion_tokens) + if ( + total_tokens is None + and prompt_tokens is not None + and completion_tokens is not None + ): + total_tokens = prompt_tokens + completion_tokens + if total_tokens is not None: + span.set_measurement("ai_total_tokens_used", total_tokens) diff --git a/aws/lambda_demo/sentry_sdk/ai/utils.py b/aws/lambda_demo/sentry_sdk/ai/utils.py new file mode 100644 index 000000000..ed3494f67 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/ai/utils.py @@ -0,0 +1,32 @@ +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + +from sentry_sdk.tracing import Span +from sentry_sdk.utils import logger + + +def _normalize_data(data): + # type: (Any) -> Any + + # convert pydantic data (e.g. OpenAI v1+) to json compatible format + if hasattr(data, "model_dump"): + try: + return data.model_dump() + except Exception as e: + logger.warning("Could not convert pydantic data to JSON: %s", e) + return data + if isinstance(data, list): + if len(data) == 1: + return _normalize_data(data[0]) # remove empty dimensions + return list(_normalize_data(x) for x in data) + if isinstance(data, dict): + return {k: _normalize_data(v) for (k, v) in data.items()} + return data + + +def set_data_normalized(span, key, value): + # type: (Span, str, Any) -> None + normalized = _normalize_data(value) + span.set_data(key, normalized) diff --git a/aws/lambda_demo/sentry_sdk/api.py b/aws/lambda_demo/sentry_sdk/api.py new file mode 100644 index 000000000..d60434079 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/api.py @@ -0,0 +1,433 @@ +import inspect +import warnings +from contextlib import contextmanager + +from sentry_sdk import tracing_utils, Client +from sentry_sdk._init_implementation import init +from sentry_sdk.consts import INSTRUMENTER +from sentry_sdk.scope import Scope, _ScopeManager, new_scope, isolation_scope +from sentry_sdk.tracing import NoOpSpan, Transaction, trace +from sentry_sdk.crons import monitor + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Mapping + + from typing import Any + from typing import Dict + from typing import Generator + from typing import Optional + from typing import overload + from typing import Callable + from typing import TypeVar + from typing import ContextManager + from typing import Union + + from typing_extensions import Unpack + + from sentry_sdk.client import BaseClient + from sentry_sdk._types import ( + Event, + Hint, + Breadcrumb, + BreadcrumbHint, + ExcInfo, + MeasurementUnit, + LogLevelStr, + SamplingContext, + ) + from sentry_sdk.tracing import Span, TransactionKwargs + + T = TypeVar("T") + F = TypeVar("F", bound=Callable[..., Any]) +else: + + def overload(x): + # type: (T) -> T + return x + + +# When changing this, update __all__ in __init__.py too +__all__ = [ + "init", + "add_breadcrumb", + "capture_event", + "capture_exception", + "capture_message", + "configure_scope", + "continue_trace", + "flush", + "get_baggage", + "get_client", + "get_global_scope", + "get_isolation_scope", + "get_current_scope", + "get_current_span", + "get_traceparent", + "is_initialized", + "isolation_scope", + "last_event_id", + "new_scope", + "push_scope", + "set_context", + "set_extra", + "set_level", + "set_measurement", + "set_tag", + "set_tags", + "set_user", + "start_span", + "start_transaction", + "trace", + "monitor", +] + + +def scopemethod(f): + # type: (F) -> F + f.__doc__ = "%s\n\n%s" % ( + "Alias for :py:meth:`sentry_sdk.Scope.%s`" % f.__name__, + inspect.getdoc(getattr(Scope, f.__name__)), + ) + return f + + +def clientmethod(f): + # type: (F) -> F + f.__doc__ = "%s\n\n%s" % ( + "Alias for :py:meth:`sentry_sdk.Client.%s`" % f.__name__, + inspect.getdoc(getattr(Client, f.__name__)), + ) + return f + + +@scopemethod +def get_client(): + # type: () -> BaseClient + return Scope.get_client() + + +def is_initialized(): + # type: () -> bool + """ + .. versionadded:: 2.0.0 + + Returns whether Sentry has been initialized or not. + + If a client is available and the client is active + (meaning it is configured to send data) then + Sentry is initialized. + """ + return get_client().is_active() + + +@scopemethod +def get_global_scope(): + # type: () -> Scope + return Scope.get_global_scope() + + +@scopemethod +def get_isolation_scope(): + # type: () -> Scope + return Scope.get_isolation_scope() + + +@scopemethod +def get_current_scope(): + # type: () -> Scope + return Scope.get_current_scope() + + +@scopemethod +def last_event_id(): + # type: () -> Optional[str] + """ + See :py:meth:`sentry_sdk.Scope.last_event_id` documentation regarding + this method's limitations. + """ + return Scope.last_event_id() + + +@scopemethod +def capture_event( + event, # type: Event + hint=None, # type: Optional[Hint] + scope=None, # type: Optional[Any] + **scope_kwargs, # type: Any +): + # type: (...) -> Optional[str] + return get_current_scope().capture_event(event, hint, scope=scope, **scope_kwargs) + + +@scopemethod +def capture_message( + message, # type: str + level=None, # type: Optional[LogLevelStr] + scope=None, # type: Optional[Any] + **scope_kwargs, # type: Any +): + # type: (...) -> Optional[str] + return get_current_scope().capture_message( + message, level, scope=scope, **scope_kwargs + ) + + +@scopemethod +def capture_exception( + error=None, # type: Optional[Union[BaseException, ExcInfo]] + scope=None, # type: Optional[Any] + **scope_kwargs, # type: Any +): + # type: (...) -> Optional[str] + return get_current_scope().capture_exception(error, scope=scope, **scope_kwargs) + + +@scopemethod +def add_breadcrumb( + crumb=None, # type: Optional[Breadcrumb] + hint=None, # type: Optional[BreadcrumbHint] + **kwargs, # type: Any +): + # type: (...) -> None + return get_isolation_scope().add_breadcrumb(crumb, hint, **kwargs) + + +@overload +def configure_scope(): + # type: () -> ContextManager[Scope] + pass + + +@overload +def configure_scope( # noqa: F811 + callback, # type: Callable[[Scope], None] +): + # type: (...) -> None + pass + + +def configure_scope( # noqa: F811 + callback=None, # type: Optional[Callable[[Scope], None]] +): + # type: (...) -> Optional[ContextManager[Scope]] + """ + Reconfigures the scope. + + :param callback: If provided, call the callback with the current scope. + + :returns: If no callback is provided, returns a context manager that returns the scope. + """ + warnings.warn( + "sentry_sdk.configure_scope is deprecated and will be removed in the next major version. " + "Please consult our migration guide to learn how to migrate to the new API: " + "https://docs.sentry.io/platforms/python/migration/1.x-to-2.x#scope-configuring", + DeprecationWarning, + stacklevel=2, + ) + + scope = get_isolation_scope() + scope.generate_propagation_context() + + if callback is not None: + # TODO: used to return None when client is None. Check if this changes behavior. + callback(scope) + + return None + + @contextmanager + def inner(): + # type: () -> Generator[Scope, None, None] + yield scope + + return inner() + + +@overload +def push_scope(): + # type: () -> ContextManager[Scope] + pass + + +@overload +def push_scope( # noqa: F811 + callback, # type: Callable[[Scope], None] +): + # type: (...) -> None + pass + + +def push_scope( # noqa: F811 + callback=None, # type: Optional[Callable[[Scope], None]] +): + # type: (...) -> Optional[ContextManager[Scope]] + """ + Pushes a new layer on the scope stack. + + :param callback: If provided, this method pushes a scope, calls + `callback`, and pops the scope again. + + :returns: If no `callback` is provided, a context manager that should + be used to pop the scope again. + """ + warnings.warn( + "sentry_sdk.push_scope is deprecated and will be removed in the next major version. " + "Please consult our migration guide to learn how to migrate to the new API: " + "https://docs.sentry.io/platforms/python/migration/1.x-to-2.x#scope-pushing", + DeprecationWarning, + stacklevel=2, + ) + + if callback is not None: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + with push_scope() as scope: + callback(scope) + return None + + return _ScopeManager() + + +@scopemethod +def set_tag(key, value): + # type: (str, Any) -> None + return get_isolation_scope().set_tag(key, value) + + +@scopemethod +def set_tags(tags): + # type: (Mapping[str, object]) -> None + return get_isolation_scope().set_tags(tags) + + +@scopemethod +def set_context(key, value): + # type: (str, Dict[str, Any]) -> None + return get_isolation_scope().set_context(key, value) + + +@scopemethod +def set_extra(key, value): + # type: (str, Any) -> None + return get_isolation_scope().set_extra(key, value) + + +@scopemethod +def set_user(value): + # type: (Optional[Dict[str, Any]]) -> None + return get_isolation_scope().set_user(value) + + +@scopemethod +def set_level(value): + # type: (LogLevelStr) -> None + return get_isolation_scope().set_level(value) + + +@clientmethod +def flush( + timeout=None, # type: Optional[float] + callback=None, # type: Optional[Callable[[int, float], None]] +): + # type: (...) -> None + return get_client().flush(timeout=timeout, callback=callback) + + +@scopemethod +def start_span( + **kwargs, # type: Any +): + # type: (...) -> Span + return get_current_scope().start_span(**kwargs) + + +@scopemethod +def start_transaction( + transaction=None, # type: Optional[Transaction] + instrumenter=INSTRUMENTER.SENTRY, # type: str + custom_sampling_context=None, # type: Optional[SamplingContext] + **kwargs, # type: Unpack[TransactionKwargs] +): + # type: (...) -> Union[Transaction, NoOpSpan] + """ + Start and return a transaction on the current scope. + + Start an existing transaction if given, otherwise create and start a new + transaction with kwargs. + + This is the entry point to manual tracing instrumentation. + + A tree structure can be built by adding child spans to the transaction, + and child spans to other spans. To start a new child span within the + transaction or any span, call the respective `.start_child()` method. + + Every child span must be finished before the transaction is finished, + otherwise the unfinished spans are discarded. + + When used as context managers, spans and transactions are automatically + finished at the end of the `with` block. If not using context managers, + call the `.finish()` method. + + When the transaction is finished, it will be sent to Sentry with all its + finished child spans. + + :param transaction: The transaction to start. If omitted, we create and + start a new transaction. + :param instrumenter: This parameter is meant for internal use only. It + will be removed in the next major version. + :param custom_sampling_context: The transaction's custom sampling context. + :param kwargs: Optional keyword arguments to be passed to the Transaction + constructor. See :py:class:`sentry_sdk.tracing.Transaction` for + available arguments. + """ + return get_current_scope().start_transaction( + transaction, instrumenter, custom_sampling_context, **kwargs + ) + + +def set_measurement(name, value, unit=""): + # type: (str, float, MeasurementUnit) -> None + transaction = get_current_scope().transaction + if transaction is not None: + transaction.set_measurement(name, value, unit) + + +def get_current_span(scope=None): + # type: (Optional[Scope]) -> Optional[Span] + """ + Returns the currently active span if there is one running, otherwise `None` + """ + return tracing_utils.get_current_span(scope) + + +def get_traceparent(): + # type: () -> Optional[str] + """ + Returns the traceparent either from the active span or from the scope. + """ + return get_current_scope().get_traceparent() + + +def get_baggage(): + # type: () -> Optional[str] + """ + Returns Baggage either from the active span or from the scope. + """ + baggage = get_current_scope().get_baggage() + if baggage is not None: + return baggage.serialize() + + return None + + +def continue_trace( + environ_or_headers, op=None, name=None, source=None, origin="manual" +): + # type: (Dict[str, Any], Optional[str], Optional[str], Optional[str], str) -> Transaction + """ + Sets the propagation context from environment or headers and returns a transaction. + """ + return get_isolation_scope().continue_trace( + environ_or_headers, op, name, source, origin + ) diff --git a/aws/lambda_demo/sentry_sdk/attachments.py b/aws/lambda_demo/sentry_sdk/attachments.py new file mode 100644 index 000000000..e5404f865 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/attachments.py @@ -0,0 +1,75 @@ +import os +import mimetypes + +from sentry_sdk.envelope import Item, PayloadRef + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Optional, Union, Callable + + +class Attachment: + """Additional files/data to send along with an event. + + This class stores attachments that can be sent along with an event. Attachments are files or other data, e.g. + config or log files, that are relevant to an event. Attachments are set on the ``Scope``, and are sent along with + all non-transaction events (or all events including transactions if ``add_to_transactions`` is ``True``) that are + captured within the ``Scope``. + + To add an attachment to a ``Scope``, use :py:meth:`sentry_sdk.Scope.add_attachment`. The parameters for + ``add_attachment`` are the same as the parameters for this class's constructor. + + :param bytes: Raw bytes of the attachment, or a function that returns the raw bytes. Must be provided unless + ``path`` is provided. + :param filename: The filename of the attachment. Must be provided unless ``path`` is provided. + :param path: Path to a file to attach. Must be provided unless ``bytes`` is provided. + :param content_type: The content type of the attachment. If not provided, it will be guessed from the ``filename`` + parameter, if available, or the ``path`` parameter if ``filename`` is ``None``. + :param add_to_transactions: Whether to add this attachment to transactions. Defaults to ``False``. + """ + + def __init__( + self, + bytes=None, # type: Union[None, bytes, Callable[[], bytes]] + filename=None, # type: Optional[str] + path=None, # type: Optional[str] + content_type=None, # type: Optional[str] + add_to_transactions=False, # type: bool + ): + # type: (...) -> None + if bytes is None and path is None: + raise TypeError("path or raw bytes required for attachment") + if filename is None and path is not None: + filename = os.path.basename(path) + if filename is None: + raise TypeError("filename is required for attachment") + if content_type is None: + content_type = mimetypes.guess_type(filename)[0] + self.bytes = bytes + self.filename = filename + self.path = path + self.content_type = content_type + self.add_to_transactions = add_to_transactions + + def to_envelope_item(self): + # type: () -> Item + """Returns an envelope item for this attachment.""" + payload = None # type: Union[None, PayloadRef, bytes] + if self.bytes is not None: + if callable(self.bytes): + payload = self.bytes() + else: + payload = self.bytes + else: + payload = PayloadRef(path=self.path) + return Item( + payload=payload, + type="attachment", + content_type=self.content_type, + filename=self.filename, + ) + + def __repr__(self): + # type: () -> str + return "" % (self.filename,) diff --git a/aws/lambda_demo/sentry_sdk/client.py b/aws/lambda_demo/sentry_sdk/client.py new file mode 100644 index 000000000..cf345c41f --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/client.py @@ -0,0 +1,941 @@ +import os +import uuid +import random +import socket +from collections.abc import Mapping +from datetime import datetime, timezone +from importlib import import_module +from typing import cast, overload + +from sentry_sdk._compat import PY37, check_uwsgi_thread_support +from sentry_sdk.utils import ( + ContextVar, + capture_internal_exceptions, + current_stacktrace, + env_to_bool, + format_timestamp, + get_sdk_name, + get_type_name, + get_default_release, + handle_in_app, + is_gevent, + logger, +) +from sentry_sdk.serializer import serialize +from sentry_sdk.tracing import trace +from sentry_sdk.transport import BaseHttpTransport, make_transport +from sentry_sdk.consts import ( + DEFAULT_MAX_VALUE_LENGTH, + DEFAULT_OPTIONS, + INSTRUMENTER, + VERSION, + ClientConstructor, +) +from sentry_sdk.integrations import _DEFAULT_INTEGRATIONS, setup_integrations +from sentry_sdk.sessions import SessionFlusher +from sentry_sdk.envelope import Envelope +from sentry_sdk.profiler.continuous_profiler import setup_continuous_profiler +from sentry_sdk.profiler.transaction_profiler import ( + has_profiling_enabled, + Profile, + setup_profiler, +) +from sentry_sdk.scrubber import EventScrubber +from sentry_sdk.monitor import Monitor +from sentry_sdk.spotlight import setup_spotlight + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import Dict + from typing import Optional + from typing import Sequence + from typing import Type + from typing import Union + from typing import TypeVar + + from sentry_sdk._types import Event, Hint, SDKInfo + from sentry_sdk.integrations import Integration + from sentry_sdk.metrics import MetricsAggregator + from sentry_sdk.scope import Scope + from sentry_sdk.session import Session + from sentry_sdk.spotlight import SpotlightClient + from sentry_sdk.transport import Transport + + I = TypeVar("I", bound=Integration) # noqa: E741 + +_client_init_debug = ContextVar("client_init_debug") + + +SDK_INFO = { + "name": "sentry.python", # SDK name will be overridden after integrations have been loaded with sentry_sdk.integrations.setup_integrations() + "version": VERSION, + "packages": [{"name": "pypi:sentry-sdk", "version": VERSION}], +} # type: SDKInfo + + +def _get_options(*args, **kwargs): + # type: (*Optional[str], **Any) -> Dict[str, Any] + if args and (isinstance(args[0], (bytes, str)) or args[0] is None): + dsn = args[0] # type: Optional[str] + args = args[1:] + else: + dsn = None + + if len(args) > 1: + raise TypeError("Only single positional argument is expected") + + rv = dict(DEFAULT_OPTIONS) + options = dict(*args, **kwargs) + if dsn is not None and options.get("dsn") is None: + options["dsn"] = dsn + + for key, value in options.items(): + if key not in rv: + raise TypeError("Unknown option %r" % (key,)) + + rv[key] = value + + if rv["dsn"] is None: + rv["dsn"] = os.environ.get("SENTRY_DSN") + + if rv["release"] is None: + rv["release"] = get_default_release() + + if rv["environment"] is None: + rv["environment"] = os.environ.get("SENTRY_ENVIRONMENT") or "production" + + if rv["debug"] is None: + rv["debug"] = env_to_bool(os.environ.get("SENTRY_DEBUG", "False"), strict=True) + + if rv["server_name"] is None and hasattr(socket, "gethostname"): + rv["server_name"] = socket.gethostname() + + if rv["instrumenter"] is None: + rv["instrumenter"] = INSTRUMENTER.SENTRY + + if rv["project_root"] is None: + try: + project_root = os.getcwd() + except Exception: + project_root = None + + rv["project_root"] = project_root + + if rv["enable_tracing"] is True and rv["traces_sample_rate"] is None: + rv["traces_sample_rate"] = 1.0 + + if rv["event_scrubber"] is None: + rv["event_scrubber"] = EventScrubber( + send_default_pii=( + False if rv["send_default_pii"] is None else rv["send_default_pii"] + ) + ) + + if rv["socket_options"] and not isinstance(rv["socket_options"], list): + logger.warning( + "Ignoring socket_options because of unexpected format. See urllib3.HTTPConnection.socket_options for the expected format." + ) + rv["socket_options"] = None + + return rv + + +try: + # Python 3.6+ + module_not_found_error = ModuleNotFoundError +except Exception: + # Older Python versions + module_not_found_error = ImportError # type: ignore + + +class BaseClient: + """ + .. versionadded:: 2.0.0 + + The basic definition of a client that is used for sending data to Sentry. + """ + + spotlight = None # type: Optional[SpotlightClient] + + def __init__(self, options=None): + # type: (Optional[Dict[str, Any]]) -> None + self.options = ( + options if options is not None else DEFAULT_OPTIONS + ) # type: Dict[str, Any] + + self.transport = None # type: Optional[Transport] + self.monitor = None # type: Optional[Monitor] + self.metrics_aggregator = None # type: Optional[MetricsAggregator] + + def __getstate__(self, *args, **kwargs): + # type: (*Any, **Any) -> Any + return {"options": {}} + + def __setstate__(self, *args, **kwargs): + # type: (*Any, **Any) -> None + pass + + @property + def dsn(self): + # type: () -> Optional[str] + return None + + def should_send_default_pii(self): + # type: () -> bool + return False + + def is_active(self): + # type: () -> bool + """ + .. versionadded:: 2.0.0 + + Returns whether the client is active (able to send data to Sentry) + """ + return False + + def capture_event(self, *args, **kwargs): + # type: (*Any, **Any) -> Optional[str] + return None + + def capture_session(self, *args, **kwargs): + # type: (*Any, **Any) -> None + return None + + if TYPE_CHECKING: + + @overload + def get_integration(self, name_or_class): + # type: (str) -> Optional[Integration] + ... + + @overload + def get_integration(self, name_or_class): + # type: (type[I]) -> Optional[I] + ... + + def get_integration(self, name_or_class): + # type: (Union[str, type[Integration]]) -> Optional[Integration] + return None + + def close(self, *args, **kwargs): + # type: (*Any, **Any) -> None + return None + + def flush(self, *args, **kwargs): + # type: (*Any, **Any) -> None + return None + + def __enter__(self): + # type: () -> BaseClient + return self + + def __exit__(self, exc_type, exc_value, tb): + # type: (Any, Any, Any) -> None + return None + + +class NonRecordingClient(BaseClient): + """ + .. versionadded:: 2.0.0 + + A client that does not send any events to Sentry. This is used as a fallback when the Sentry SDK is not yet initialized. + """ + + pass + + +class _Client(BaseClient): + """ + The client is internally responsible for capturing the events and + forwarding them to sentry through the configured transport. It takes + the client options as keyword arguments and optionally the DSN as first + argument. + + Alias of :py:class:`sentry_sdk.Client`. (Was created for better intelisense support) + """ + + def __init__(self, *args, **kwargs): + # type: (*Any, **Any) -> None + super(_Client, self).__init__(options=get_options(*args, **kwargs)) + self._init_impl() + + def __getstate__(self): + # type: () -> Any + return {"options": self.options} + + def __setstate__(self, state): + # type: (Any) -> None + self.options = state["options"] + self._init_impl() + + def _setup_instrumentation(self, functions_to_trace): + # type: (Sequence[Dict[str, str]]) -> None + """ + Instruments the functions given in the list `functions_to_trace` with the `@sentry_sdk.tracing.trace` decorator. + """ + for function in functions_to_trace: + class_name = None + function_qualname = function["qualified_name"] + module_name, function_name = function_qualname.rsplit(".", 1) + + try: + # Try to import module and function + # ex: "mymodule.submodule.funcname" + + module_obj = import_module(module_name) + function_obj = getattr(module_obj, function_name) + setattr(module_obj, function_name, trace(function_obj)) + logger.debug("Enabled tracing for %s", function_qualname) + except module_not_found_error: + try: + # Try to import a class + # ex: "mymodule.submodule.MyClassName.member_function" + + module_name, class_name = module_name.rsplit(".", 1) + module_obj = import_module(module_name) + class_obj = getattr(module_obj, class_name) + function_obj = getattr(class_obj, function_name) + function_type = type(class_obj.__dict__[function_name]) + traced_function = trace(function_obj) + + if function_type in (staticmethod, classmethod): + traced_function = staticmethod(traced_function) + + setattr(class_obj, function_name, traced_function) + setattr(module_obj, class_name, class_obj) + logger.debug("Enabled tracing for %s", function_qualname) + + except Exception as e: + logger.warning( + "Can not enable tracing for '%s'. (%s) Please check your `functions_to_trace` parameter.", + function_qualname, + e, + ) + + except Exception as e: + logger.warning( + "Can not enable tracing for '%s'. (%s) Please check your `functions_to_trace` parameter.", + function_qualname, + e, + ) + + def _init_impl(self): + # type: () -> None + old_debug = _client_init_debug.get(False) + + def _capture_envelope(envelope): + # type: (Envelope) -> None + if self.transport is not None: + self.transport.capture_envelope(envelope) + + try: + _client_init_debug.set(self.options["debug"]) + self.transport = make_transport(self.options) + + self.monitor = None + if self.transport: + if self.options["enable_backpressure_handling"]: + self.monitor = Monitor(self.transport) + + self.session_flusher = SessionFlusher(capture_func=_capture_envelope) + + self.metrics_aggregator = None # type: Optional[MetricsAggregator] + experiments = self.options.get("_experiments", {}) + if experiments.get("enable_metrics", True): + # Context vars are not working correctly on Python <=3.6 + # with gevent. + metrics_supported = not is_gevent() or PY37 + if metrics_supported: + from sentry_sdk.metrics import MetricsAggregator + + self.metrics_aggregator = MetricsAggregator( + capture_func=_capture_envelope, + enable_code_locations=bool( + experiments.get("metric_code_locations", True) + ), + ) + else: + logger.info( + "Metrics not supported on Python 3.6 and lower with gevent." + ) + + max_request_body_size = ("always", "never", "small", "medium") + if self.options["max_request_body_size"] not in max_request_body_size: + raise ValueError( + "Invalid value for max_request_body_size. Must be one of {}".format( + max_request_body_size + ) + ) + + if self.options["_experiments"].get("otel_powered_performance", False): + logger.debug( + "[OTel] Enabling experimental OTel-powered performance monitoring." + ) + self.options["instrumenter"] = INSTRUMENTER.OTEL + if ( + "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration" + not in _DEFAULT_INTEGRATIONS + ): + _DEFAULT_INTEGRATIONS.append( + "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration", + ) + + self.integrations = setup_integrations( + self.options["integrations"], + with_defaults=self.options["default_integrations"], + with_auto_enabling_integrations=self.options[ + "auto_enabling_integrations" + ], + disabled_integrations=self.options["disabled_integrations"], + ) + + spotlight_config = self.options.get("spotlight") + if spotlight_config is None and "SENTRY_SPOTLIGHT" in os.environ: + spotlight_env_value = os.environ["SENTRY_SPOTLIGHT"] + spotlight_config = env_to_bool(spotlight_env_value, strict=True) + self.options["spotlight"] = ( + spotlight_config + if spotlight_config is not None + else spotlight_env_value + ) + + if self.options.get("spotlight"): + self.spotlight = setup_spotlight(self.options) + + sdk_name = get_sdk_name(list(self.integrations.keys())) + SDK_INFO["name"] = sdk_name + logger.debug("Setting SDK name to '%s'", sdk_name) + + if has_profiling_enabled(self.options): + try: + setup_profiler(self.options) + except Exception as e: + logger.debug("Can not set up profiler. (%s)", e) + else: + try: + setup_continuous_profiler( + self.options, + sdk_info=SDK_INFO, + capture_func=_capture_envelope, + ) + except Exception as e: + logger.debug("Can not set up continuous profiler. (%s)", e) + + finally: + _client_init_debug.set(old_debug) + + self._setup_instrumentation(self.options.get("functions_to_trace", [])) + + if ( + self.monitor + or self.metrics_aggregator + or has_profiling_enabled(self.options) + or isinstance(self.transport, BaseHttpTransport) + ): + # If we have anything on that could spawn a background thread, we + # need to check if it's safe to use them. + check_uwsgi_thread_support() + + def is_active(self): + # type: () -> bool + """ + .. versionadded:: 2.0.0 + + Returns whether the client is active (able to send data to Sentry) + """ + return True + + def should_send_default_pii(self): + # type: () -> bool + """ + .. versionadded:: 2.0.0 + + Returns whether the client should send default PII (Personally Identifiable Information) data to Sentry. + """ + result = self.options.get("send_default_pii") + if result is None: + result = not self.options["dsn"] and self.spotlight is not None + + return result + + @property + def dsn(self): + # type: () -> Optional[str] + """Returns the configured DSN as string.""" + return self.options["dsn"] + + def _prepare_event( + self, + event, # type: Event + hint, # type: Hint + scope, # type: Optional[Scope] + ): + # type: (...) -> Optional[Event] + + if event.get("timestamp") is None: + event["timestamp"] = datetime.now(timezone.utc) + + if scope is not None: + is_transaction = event.get("type") == "transaction" + spans_before = len(event.get("spans", [])) + event_ = scope.apply_to_event(event, hint, self.options) + + # one of the event/error processors returned None + if event_ is None: + if self.transport: + self.transport.record_lost_event( + "event_processor", + data_category=("transaction" if is_transaction else "error"), + ) + if is_transaction: + self.transport.record_lost_event( + "event_processor", + data_category="span", + quantity=spans_before + 1, # +1 for the transaction itself + ) + return None + + event = event_ + + spans_delta = spans_before - len(event.get("spans", [])) + if is_transaction and spans_delta > 0 and self.transport is not None: + self.transport.record_lost_event( + "event_processor", data_category="span", quantity=spans_delta + ) + + if ( + self.options["attach_stacktrace"] + and "exception" not in event + and "stacktrace" not in event + and "threads" not in event + ): + with capture_internal_exceptions(): + event["threads"] = { + "values": [ + { + "stacktrace": current_stacktrace( + include_local_variables=self.options.get( + "include_local_variables", True + ), + max_value_length=self.options.get( + "max_value_length", DEFAULT_MAX_VALUE_LENGTH + ), + ), + "crashed": False, + "current": True, + } + ] + } + + for key in "release", "environment", "server_name", "dist": + if event.get(key) is None and self.options[key] is not None: + event[key] = str(self.options[key]).strip() + if event.get("sdk") is None: + sdk_info = dict(SDK_INFO) + sdk_info["integrations"] = sorted(self.integrations.keys()) + event["sdk"] = sdk_info + + if event.get("platform") is None: + event["platform"] = "python" + + event = handle_in_app( + event, + self.options["in_app_exclude"], + self.options["in_app_include"], + self.options["project_root"], + ) + + if event is not None: + event_scrubber = self.options["event_scrubber"] + if event_scrubber: + event_scrubber.scrub_event(event) + + # Postprocess the event here so that annotated types do + # generally not surface in before_send + if event is not None: + event = cast( + "Event", + serialize( + cast("Dict[str, Any]", event), + max_request_body_size=self.options.get("max_request_body_size"), + max_value_length=self.options.get("max_value_length"), + custom_repr=self.options.get("custom_repr"), + ), + ) + + before_send = self.options["before_send"] + if ( + before_send is not None + and event is not None + and event.get("type") != "transaction" + ): + new_event = None + with capture_internal_exceptions(): + new_event = before_send(event, hint or {}) + if new_event is None: + logger.info("before send dropped event") + if self.transport: + self.transport.record_lost_event( + "before_send", data_category="error" + ) + event = new_event + + before_send_transaction = self.options["before_send_transaction"] + if ( + before_send_transaction is not None + and event is not None + and event.get("type") == "transaction" + ): + new_event = None + spans_before = len(event.get("spans", [])) + with capture_internal_exceptions(): + new_event = before_send_transaction(event, hint or {}) + if new_event is None: + logger.info("before send transaction dropped event") + if self.transport: + self.transport.record_lost_event( + reason="before_send", data_category="transaction" + ) + self.transport.record_lost_event( + reason="before_send", + data_category="span", + quantity=spans_before + 1, # +1 for the transaction itself + ) + else: + spans_delta = spans_before - len(new_event.get("spans", [])) + if spans_delta > 0 and self.transport is not None: + self.transport.record_lost_event( + reason="before_send", data_category="span", quantity=spans_delta + ) + + event = new_event + + return event + + def _is_ignored_error(self, event, hint): + # type: (Event, Hint) -> bool + exc_info = hint.get("exc_info") + if exc_info is None: + return False + + error = exc_info[0] + error_type_name = get_type_name(exc_info[0]) + error_full_name = "%s.%s" % (exc_info[0].__module__, error_type_name) + + for ignored_error in self.options["ignore_errors"]: + # String types are matched against the type name in the + # exception only + if isinstance(ignored_error, str): + if ignored_error == error_full_name or ignored_error == error_type_name: + return True + else: + if issubclass(error, ignored_error): + return True + + return False + + def _should_capture( + self, + event, # type: Event + hint, # type: Hint + scope=None, # type: Optional[Scope] + ): + # type: (...) -> bool + # Transactions are sampled independent of error events. + is_transaction = event.get("type") == "transaction" + if is_transaction: + return True + + ignoring_prevents_recursion = scope is not None and not scope._should_capture + if ignoring_prevents_recursion: + return False + + ignored_by_config_option = self._is_ignored_error(event, hint) + if ignored_by_config_option: + return False + + return True + + def _should_sample_error( + self, + event, # type: Event + hint, # type: Hint + ): + # type: (...) -> bool + error_sampler = self.options.get("error_sampler", None) + + if callable(error_sampler): + with capture_internal_exceptions(): + sample_rate = error_sampler(event, hint) + else: + sample_rate = self.options["sample_rate"] + + try: + not_in_sample_rate = sample_rate < 1.0 and random.random() >= sample_rate + except NameError: + logger.warning( + "The provided error_sampler raised an error. Defaulting to sampling the event." + ) + + # If the error_sampler raised an error, we should sample the event, since the default behavior + # (when no sample_rate or error_sampler is provided) is to sample all events. + not_in_sample_rate = False + except TypeError: + parameter, verb = ( + ("error_sampler", "returned") + if callable(error_sampler) + else ("sample_rate", "contains") + ) + logger.warning( + "The provided %s %s an invalid value of %s. The value should be a float or a bool. Defaulting to sampling the event." + % (parameter, verb, repr(sample_rate)) + ) + + # If the sample_rate has an invalid value, we should sample the event, since the default behavior + # (when no sample_rate or error_sampler is provided) is to sample all events. + not_in_sample_rate = False + + if not_in_sample_rate: + # because we will not sample this event, record a "lost event". + if self.transport: + self.transport.record_lost_event("sample_rate", data_category="error") + + return False + + return True + + def _update_session_from_event( + self, + session, # type: Session + event, # type: Event + ): + # type: (...) -> None + + crashed = False + errored = False + user_agent = None + + exceptions = (event.get("exception") or {}).get("values") + if exceptions: + errored = True + for error in exceptions: + mechanism = error.get("mechanism") + if isinstance(mechanism, Mapping) and mechanism.get("handled") is False: + crashed = True + break + + user = event.get("user") + + if session.user_agent is None: + headers = (event.get("request") or {}).get("headers") + headers_dict = headers if isinstance(headers, dict) else {} + for k, v in headers_dict.items(): + if k.lower() == "user-agent": + user_agent = v + break + + session.update( + status="crashed" if crashed else None, + user=user, + user_agent=user_agent, + errors=session.errors + (errored or crashed), + ) + + def capture_event( + self, + event, # type: Event + hint=None, # type: Optional[Hint] + scope=None, # type: Optional[Scope] + ): + # type: (...) -> Optional[str] + """Captures an event. + + :param event: A ready-made event that can be directly sent to Sentry. + + :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object. + + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + + :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help. + """ + hint = dict(hint or ()) # type: Hint + + if not self._should_capture(event, hint, scope): + return None + + profile = event.pop("profile", None) + + event_id = event.get("event_id") + if event_id is None: + event["event_id"] = event_id = uuid.uuid4().hex + event_opt = self._prepare_event(event, hint, scope) + if event_opt is None: + return None + + # whenever we capture an event we also check if the session needs + # to be updated based on that information. + session = scope._session if scope else None + if session: + self._update_session_from_event(session, event) + + is_transaction = event_opt.get("type") == "transaction" + is_checkin = event_opt.get("type") == "check_in" + + if ( + not is_transaction + and not is_checkin + and not self._should_sample_error(event, hint) + ): + return None + + attachments = hint.get("attachments") + + trace_context = event_opt.get("contexts", {}).get("trace") or {} + dynamic_sampling_context = trace_context.pop("dynamic_sampling_context", {}) + + headers = { + "event_id": event_opt["event_id"], + "sent_at": format_timestamp(datetime.now(timezone.utc)), + } # type: dict[str, object] + + if dynamic_sampling_context: + headers["trace"] = dynamic_sampling_context + + envelope = Envelope(headers=headers) + + if is_transaction: + if isinstance(profile, Profile): + envelope.add_profile(profile.to_json(event_opt, self.options)) + envelope.add_transaction(event_opt) + elif is_checkin: + envelope.add_checkin(event_opt) + else: + envelope.add_event(event_opt) + + for attachment in attachments or (): + envelope.add_item(attachment.to_envelope_item()) + + return_value = None + if self.spotlight: + self.spotlight.capture_envelope(envelope) + return_value = event_id + + if self.transport is not None: + self.transport.capture_envelope(envelope) + return_value = event_id + + return return_value + + def capture_session( + self, session # type: Session + ): + # type: (...) -> None + if not session.release: + logger.info("Discarded session update because of missing release") + else: + self.session_flusher.add_session(session) + + if TYPE_CHECKING: + + @overload + def get_integration(self, name_or_class): + # type: (str) -> Optional[Integration] + ... + + @overload + def get_integration(self, name_or_class): + # type: (type[I]) -> Optional[I] + ... + + def get_integration( + self, name_or_class # type: Union[str, Type[Integration]] + ): + # type: (...) -> Optional[Integration] + """Returns the integration for this client by name or class. + If the client does not have that integration then `None` is returned. + """ + if isinstance(name_or_class, str): + integration_name = name_or_class + elif name_or_class.identifier is not None: + integration_name = name_or_class.identifier + else: + raise ValueError("Integration has no name") + + return self.integrations.get(integration_name) + + def close( + self, + timeout=None, # type: Optional[float] + callback=None, # type: Optional[Callable[[int, float], None]] + ): + # type: (...) -> None + """ + Close the client and shut down the transport. Arguments have the same + semantics as :py:meth:`Client.flush`. + """ + if self.transport is not None: + self.flush(timeout=timeout, callback=callback) + self.session_flusher.kill() + if self.metrics_aggregator is not None: + self.metrics_aggregator.kill() + if self.monitor: + self.monitor.kill() + self.transport.kill() + self.transport = None + + def flush( + self, + timeout=None, # type: Optional[float] + callback=None, # type: Optional[Callable[[int, float], None]] + ): + # type: (...) -> None + """ + Wait for the current events to be sent. + + :param timeout: Wait for at most `timeout` seconds. If no `timeout` is provided, the `shutdown_timeout` option value is used. + + :param callback: Is invoked with the number of pending events and the configured timeout. + """ + if self.transport is not None: + if timeout is None: + timeout = self.options["shutdown_timeout"] + self.session_flusher.flush() + if self.metrics_aggregator is not None: + self.metrics_aggregator.flush() + self.transport.flush(timeout=timeout, callback=callback) + + def __enter__(self): + # type: () -> _Client + return self + + def __exit__(self, exc_type, exc_value, tb): + # type: (Any, Any, Any) -> None + self.close() + + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + # Make mypy, PyCharm and other static analyzers think `get_options` is a + # type to have nicer autocompletion for params. + # + # Use `ClientConstructor` to define the argument types of `init` and + # `Dict[str, Any]` to tell static analyzers about the return type. + + class get_options(ClientConstructor, Dict[str, Any]): # noqa: N801 + pass + + class Client(ClientConstructor, _Client): + pass + +else: + # Alias `get_options` for actual usage. Go through the lambda indirection + # to throw PyCharm off of the weakly typed signature (it would otherwise + # discover both the weakly typed signature of `_init` and our faked `init` + # type). + + get_options = (lambda: _get_options)() + Client = (lambda: _Client)() diff --git a/aws/lambda_demo/sentry_sdk/consts.py b/aws/lambda_demo/sentry_sdk/consts.py new file mode 100644 index 000000000..23f79ebd6 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/consts.py @@ -0,0 +1,584 @@ +import itertools + +from enum import Enum +from typing import TYPE_CHECKING + +# up top to prevent circular import due to integration import +DEFAULT_MAX_VALUE_LENGTH = 1024 + +DEFAULT_MAX_STACK_FRAMES = 100 +DEFAULT_ADD_FULL_STACK = False + + +# Also needs to be at the top to prevent circular import +class EndpointType(Enum): + """ + The type of an endpoint. This is an enum, rather than a constant, for historical reasons + (the old /store endpoint). The enum also preserve future compatibility, in case we ever + have a new endpoint. + """ + + ENVELOPE = "envelope" + + +class CompressionAlgo(Enum): + GZIP = "gzip" + BROTLI = "br" + + +if TYPE_CHECKING: + import sentry_sdk + + from typing import Optional + from typing import Callable + from typing import Union + from typing import List + from typing import Type + from typing import Dict + from typing import Any + from typing import Sequence + from typing import Tuple + from typing_extensions import TypedDict + + from sentry_sdk._types import ( + BreadcrumbProcessor, + ContinuousProfilerMode, + Event, + EventProcessor, + Hint, + MeasurementUnit, + ProfilerMode, + TracesSampler, + TransactionProcessor, + MetricTags, + MetricValue, + ) + + # Experiments are feature flags to enable and disable certain unstable SDK + # functionality. Changing them from the defaults (`None`) in production + # code is highly discouraged. They are not subject to any stability + # guarantees such as the ones from semantic versioning. + Experiments = TypedDict( + "Experiments", + { + "max_spans": Optional[int], + "max_flags": Optional[int], + "record_sql_params": Optional[bool], + "continuous_profiling_auto_start": Optional[bool], + "continuous_profiling_mode": Optional[ContinuousProfilerMode], + "otel_powered_performance": Optional[bool], + "transport_zlib_compression_level": Optional[int], + "transport_compression_level": Optional[int], + "transport_compression_algo": Optional[CompressionAlgo], + "transport_num_pools": Optional[int], + "transport_http2": Optional[bool], + "enable_metrics": Optional[bool], + "before_emit_metric": Optional[ + Callable[[str, MetricValue, MeasurementUnit, MetricTags], bool] + ], + "metric_code_locations": Optional[bool], + }, + total=False, + ) + +DEFAULT_QUEUE_SIZE = 100 +DEFAULT_MAX_BREADCRUMBS = 100 +MATCH_ALL = r".*" + +FALSE_VALUES = [ + "false", + "no", + "off", + "n", + "0", +] + + +class INSTRUMENTER: + SENTRY = "sentry" + OTEL = "otel" + + +class SPANDATA: + """ + Additional information describing the type of the span. + See: https://develop.sentry.dev/sdk/performance/span-data-conventions/ + """ + + AI_FREQUENCY_PENALTY = "ai.frequency_penalty" + """ + Used to reduce repetitiveness of generated tokens. + Example: 0.5 + """ + + AI_PRESENCE_PENALTY = "ai.presence_penalty" + """ + Used to reduce repetitiveness of generated tokens. + Example: 0.5 + """ + + AI_INPUT_MESSAGES = "ai.input_messages" + """ + The input messages to an LLM call. + Example: [{"role": "user", "message": "hello"}] + """ + + AI_MODEL_ID = "ai.model_id" + """ + The unique descriptor of the model being execugted + Example: gpt-4 + """ + + AI_METADATA = "ai.metadata" + """ + Extra metadata passed to an AI pipeline step. + Example: {"executed_function": "add_integers"} + """ + + AI_TAGS = "ai.tags" + """ + Tags that describe an AI pipeline step. + Example: {"executed_function": "add_integers"} + """ + + AI_STREAMING = "ai.streaming" + """ + Whether or not the AI model call's repsonse was streamed back asynchronously + Example: true + """ + + AI_TEMPERATURE = "ai.temperature" + """ + For an AI model call, the temperature parameter. Temperature essentially means how random the output will be. + Example: 0.5 + """ + + AI_TOP_P = "ai.top_p" + """ + For an AI model call, the top_p parameter. Top_p essentially controls how random the output will be. + Example: 0.5 + """ + + AI_TOP_K = "ai.top_k" + """ + For an AI model call, the top_k parameter. Top_k essentially controls how random the output will be. + Example: 35 + """ + + AI_FUNCTION_CALL = "ai.function_call" + """ + For an AI model call, the function that was called. This is deprecated for OpenAI, and replaced by tool_calls + """ + + AI_TOOL_CALLS = "ai.tool_calls" + """ + For an AI model call, the function that was called. This is deprecated for OpenAI, and replaced by tool_calls + """ + + AI_TOOLS = "ai.tools" + """ + For an AI model call, the functions that are available + """ + + AI_RESPONSE_FORMAT = "ai.response_format" + """ + For an AI model call, the format of the response + """ + + AI_LOGIT_BIAS = "ai.response_format" + """ + For an AI model call, the logit bias + """ + + AI_PREAMBLE = "ai.preamble" + """ + For an AI model call, the preamble parameter. + Preambles are a part of the prompt used to adjust the model's overall behavior and conversation style. + Example: "You are now a clown." + """ + + AI_RAW_PROMPTING = "ai.raw_prompting" + """ + Minimize pre-processing done to the prompt sent to the LLM. + Example: true + """ + + AI_RESPONSES = "ai.responses" + """ + The responses to an AI model call. Always as a list. + Example: ["hello", "world"] + """ + + AI_SEED = "ai.seed" + """ + The seed, ideally models given the same seed and same other parameters will produce the exact same output. + Example: 123.45 + """ + + DB_NAME = "db.name" + """ + The name of the database being accessed. For commands that switch the database, this should be set to the target database (even if the command fails). + Example: myDatabase + """ + + DB_USER = "db.user" + """ + The name of the database user used for connecting to the database. + See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md + Example: my_user + """ + + DB_OPERATION = "db.operation" + """ + The name of the operation being executed, e.g. the MongoDB command name such as findAndModify, or the SQL keyword. + See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md + Example: findAndModify, HMSET, SELECT + """ + + DB_SYSTEM = "db.system" + """ + An identifier for the database management system (DBMS) product being used. + See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md + Example: postgresql + """ + + DB_MONGODB_COLLECTION = "db.mongodb.collection" + """ + The MongoDB collection being accessed within the database. + See: https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/mongodb.md#attributes + Example: public.users; customers + """ + + CACHE_HIT = "cache.hit" + """ + A boolean indicating whether the requested data was found in the cache. + Example: true + """ + + CACHE_ITEM_SIZE = "cache.item_size" + """ + The size of the requested data in bytes. + Example: 58 + """ + + CACHE_KEY = "cache.key" + """ + The key of the requested data. + Example: template.cache.some_item.867da7e2af8e6b2f3aa7213a4080edb3 + """ + + NETWORK_PEER_ADDRESS = "network.peer.address" + """ + Peer address of the network connection - IP address or Unix domain socket name. + Example: 10.1.2.80, /tmp/my.sock, localhost + """ + + NETWORK_PEER_PORT = "network.peer.port" + """ + Peer port number of the network connection. + Example: 6379 + """ + + HTTP_QUERY = "http.query" + """ + The Query string present in the URL. + Example: ?foo=bar&bar=baz + """ + + HTTP_FRAGMENT = "http.fragment" + """ + The Fragments present in the URL. + Example: #foo=bar + """ + + HTTP_METHOD = "http.method" + """ + The HTTP method used. + Example: GET + """ + + HTTP_STATUS_CODE = "http.response.status_code" + """ + The HTTP status code as an integer. + Example: 418 + """ + + MESSAGING_DESTINATION_NAME = "messaging.destination.name" + """ + The destination name where the message is being consumed from, + e.g. the queue name or topic. + """ + + MESSAGING_MESSAGE_ID = "messaging.message.id" + """ + The message's identifier. + """ + + MESSAGING_MESSAGE_RETRY_COUNT = "messaging.message.retry.count" + """ + Number of retries/attempts to process a message. + """ + + MESSAGING_MESSAGE_RECEIVE_LATENCY = "messaging.message.receive.latency" + """ + The latency between when the task was enqueued and when it was started to be processed. + """ + + MESSAGING_SYSTEM = "messaging.system" + """ + The messaging system's name, e.g. `kafka`, `aws_sqs` + """ + + SERVER_ADDRESS = "server.address" + """ + Name of the database host. + Example: example.com + """ + + SERVER_PORT = "server.port" + """ + Logical server port number + Example: 80; 8080; 443 + """ + + SERVER_SOCKET_ADDRESS = "server.socket.address" + """ + Physical server IP address or Unix socket address. + Example: 10.5.3.2 + """ + + SERVER_SOCKET_PORT = "server.socket.port" + """ + Physical server port. + Recommended: If different than server.port. + Example: 16456 + """ + + CODE_FILEPATH = "code.filepath" + """ + The source code file name that identifies the code unit as uniquely as possible (preferably an absolute file path). + Example: "/app/myapplication/http/handler/server.py" + """ + + CODE_LINENO = "code.lineno" + """ + The line number in `code.filepath` best representing the operation. It SHOULD point within the code unit named in `code.function`. + Example: 42 + """ + + CODE_FUNCTION = "code.function" + """ + The method or function name, or equivalent (usually rightmost part of the code unit's name). + Example: "server_request" + """ + + CODE_NAMESPACE = "code.namespace" + """ + The "namespace" within which `code.function` is defined. Usually the qualified class or module name, such that `code.namespace` + some separator + `code.function` form a unique identifier for the code unit. + Example: "http.handler" + """ + + THREAD_ID = "thread.id" + """ + Identifier of a thread from where the span originated. This should be a string. + Example: "7972576320" + """ + + THREAD_NAME = "thread.name" + """ + Label identifying a thread from where the span originated. This should be a string. + Example: "MainThread" + """ + + PROFILER_ID = "profiler_id" + """ + Label identifying the profiler id that the span occurred in. This should be a string. + Example: "5249fbada8d5416482c2f6e47e337372" + """ + + +class SPANSTATUS: + """ + The status of a Sentry span. + + See: https://develop.sentry.dev/sdk/event-payloads/contexts/#trace-context + """ + + ABORTED = "aborted" + ALREADY_EXISTS = "already_exists" + CANCELLED = "cancelled" + DATA_LOSS = "data_loss" + DEADLINE_EXCEEDED = "deadline_exceeded" + FAILED_PRECONDITION = "failed_precondition" + INTERNAL_ERROR = "internal_error" + INVALID_ARGUMENT = "invalid_argument" + NOT_FOUND = "not_found" + OK = "ok" + OUT_OF_RANGE = "out_of_range" + PERMISSION_DENIED = "permission_denied" + RESOURCE_EXHAUSTED = "resource_exhausted" + UNAUTHENTICATED = "unauthenticated" + UNAVAILABLE = "unavailable" + UNIMPLEMENTED = "unimplemented" + UNKNOWN_ERROR = "unknown_error" + + +class OP: + ANTHROPIC_MESSAGES_CREATE = "ai.messages.create.anthropic" + CACHE_GET = "cache.get" + CACHE_PUT = "cache.put" + COHERE_CHAT_COMPLETIONS_CREATE = "ai.chat_completions.create.cohere" + COHERE_EMBEDDINGS_CREATE = "ai.embeddings.create.cohere" + DB = "db" + DB_REDIS = "db.redis" + EVENT_DJANGO = "event.django" + FUNCTION = "function" + FUNCTION_AWS = "function.aws" + FUNCTION_GCP = "function.gcp" + GRAPHQL_EXECUTE = "graphql.execute" + GRAPHQL_MUTATION = "graphql.mutation" + GRAPHQL_PARSE = "graphql.parse" + GRAPHQL_RESOLVE = "graphql.resolve" + GRAPHQL_SUBSCRIPTION = "graphql.subscription" + GRAPHQL_QUERY = "graphql.query" + GRAPHQL_VALIDATE = "graphql.validate" + GRPC_CLIENT = "grpc.client" + GRPC_SERVER = "grpc.server" + HTTP_CLIENT = "http.client" + HTTP_CLIENT_STREAM = "http.client.stream" + HTTP_SERVER = "http.server" + MIDDLEWARE_DJANGO = "middleware.django" + MIDDLEWARE_LITESTAR = "middleware.litestar" + MIDDLEWARE_LITESTAR_RECEIVE = "middleware.litestar.receive" + MIDDLEWARE_LITESTAR_SEND = "middleware.litestar.send" + MIDDLEWARE_STARLETTE = "middleware.starlette" + MIDDLEWARE_STARLETTE_RECEIVE = "middleware.starlette.receive" + MIDDLEWARE_STARLETTE_SEND = "middleware.starlette.send" + MIDDLEWARE_STARLITE = "middleware.starlite" + MIDDLEWARE_STARLITE_RECEIVE = "middleware.starlite.receive" + MIDDLEWARE_STARLITE_SEND = "middleware.starlite.send" + OPENAI_CHAT_COMPLETIONS_CREATE = "ai.chat_completions.create.openai" + OPENAI_EMBEDDINGS_CREATE = "ai.embeddings.create.openai" + HUGGINGFACE_HUB_CHAT_COMPLETIONS_CREATE = ( + "ai.chat_completions.create.huggingface_hub" + ) + LANGCHAIN_PIPELINE = "ai.pipeline.langchain" + LANGCHAIN_RUN = "ai.run.langchain" + LANGCHAIN_TOOL = "ai.tool.langchain" + LANGCHAIN_AGENT = "ai.agent.langchain" + LANGCHAIN_CHAT_COMPLETIONS_CREATE = "ai.chat_completions.create.langchain" + QUEUE_PROCESS = "queue.process" + QUEUE_PUBLISH = "queue.publish" + QUEUE_SUBMIT_ARQ = "queue.submit.arq" + QUEUE_TASK_ARQ = "queue.task.arq" + QUEUE_SUBMIT_CELERY = "queue.submit.celery" + QUEUE_TASK_CELERY = "queue.task.celery" + QUEUE_TASK_RQ = "queue.task.rq" + QUEUE_SUBMIT_HUEY = "queue.submit.huey" + QUEUE_TASK_HUEY = "queue.task.huey" + QUEUE_SUBMIT_RAY = "queue.submit.ray" + QUEUE_TASK_RAY = "queue.task.ray" + SUBPROCESS = "subprocess" + SUBPROCESS_WAIT = "subprocess.wait" + SUBPROCESS_COMMUNICATE = "subprocess.communicate" + TEMPLATE_RENDER = "template.render" + VIEW_RENDER = "view.render" + VIEW_RESPONSE_RENDER = "view.response.render" + WEBSOCKET_SERVER = "websocket.server" + SOCKET_CONNECTION = "socket.connection" + SOCKET_DNS = "socket.dns" + + +# This type exists to trick mypy and PyCharm into thinking `init` and `Client` +# take these arguments (even though they take opaque **kwargs) +class ClientConstructor: + + def __init__( + self, + dsn=None, # type: Optional[str] + *, + max_breadcrumbs=DEFAULT_MAX_BREADCRUMBS, # type: int + release=None, # type: Optional[str] + environment=None, # type: Optional[str] + server_name=None, # type: Optional[str] + shutdown_timeout=2, # type: float + integrations=[], # type: Sequence[sentry_sdk.integrations.Integration] # noqa: B006 + in_app_include=[], # type: List[str] # noqa: B006 + in_app_exclude=[], # type: List[str] # noqa: B006 + default_integrations=True, # type: bool + dist=None, # type: Optional[str] + transport=None, # type: Optional[Union[sentry_sdk.transport.Transport, Type[sentry_sdk.transport.Transport], Callable[[Event], None]]] + transport_queue_size=DEFAULT_QUEUE_SIZE, # type: int + sample_rate=1.0, # type: float + send_default_pii=None, # type: Optional[bool] + http_proxy=None, # type: Optional[str] + https_proxy=None, # type: Optional[str] + ignore_errors=[], # type: Sequence[Union[type, str]] # noqa: B006 + max_request_body_size="medium", # type: str + socket_options=None, # type: Optional[List[Tuple[int, int, int | bytes]]] + keep_alive=False, # type: bool + before_send=None, # type: Optional[EventProcessor] + before_breadcrumb=None, # type: Optional[BreadcrumbProcessor] + debug=None, # type: Optional[bool] + attach_stacktrace=False, # type: bool + ca_certs=None, # type: Optional[str] + propagate_traces=True, # type: bool + traces_sample_rate=None, # type: Optional[float] + traces_sampler=None, # type: Optional[TracesSampler] + profiles_sample_rate=None, # type: Optional[float] + profiles_sampler=None, # type: Optional[TracesSampler] + profiler_mode=None, # type: Optional[ProfilerMode] + auto_enabling_integrations=True, # type: bool + disabled_integrations=None, # type: Optional[Sequence[sentry_sdk.integrations.Integration]] + auto_session_tracking=True, # type: bool + send_client_reports=True, # type: bool + _experiments={}, # type: Experiments # noqa: B006 + proxy_headers=None, # type: Optional[Dict[str, str]] + instrumenter=INSTRUMENTER.SENTRY, # type: Optional[str] + before_send_transaction=None, # type: Optional[TransactionProcessor] + project_root=None, # type: Optional[str] + enable_tracing=None, # type: Optional[bool] + include_local_variables=True, # type: Optional[bool] + include_source_context=True, # type: Optional[bool] + trace_propagation_targets=[ # noqa: B006 + MATCH_ALL + ], # type: Optional[Sequence[str]] + functions_to_trace=[], # type: Sequence[Dict[str, str]] # noqa: B006 + event_scrubber=None, # type: Optional[sentry_sdk.scrubber.EventScrubber] + max_value_length=DEFAULT_MAX_VALUE_LENGTH, # type: int + enable_backpressure_handling=True, # type: bool + error_sampler=None, # type: Optional[Callable[[Event, Hint], Union[float, bool]]] + enable_db_query_source=True, # type: bool + db_query_source_threshold_ms=100, # type: int + spotlight=None, # type: Optional[Union[bool, str]] + cert_file=None, # type: Optional[str] + key_file=None, # type: Optional[str] + custom_repr=None, # type: Optional[Callable[..., Optional[str]]] + add_full_stack=DEFAULT_ADD_FULL_STACK, # type: bool + max_stack_frames=DEFAULT_MAX_STACK_FRAMES, # type: Optional[int] + ): + # type: (...) -> None + pass + + +def _get_default_options(): + # type: () -> dict[str, Any] + import inspect + + a = inspect.getfullargspec(ClientConstructor.__init__) + defaults = a.defaults or () + kwonlydefaults = a.kwonlydefaults or {} + + return dict( + itertools.chain( + zip(a.args[-len(defaults) :], defaults), + kwonlydefaults.items(), + ) + ) + + +DEFAULT_OPTIONS = _get_default_options() +del _get_default_options + + +VERSION = "2.20.0" diff --git a/aws/lambda_demo/sentry_sdk/crons/__init__.py b/aws/lambda_demo/sentry_sdk/crons/__init__.py new file mode 100644 index 000000000..6f748aaec --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/crons/__init__.py @@ -0,0 +1,10 @@ +from sentry_sdk.crons.api import capture_checkin +from sentry_sdk.crons.consts import MonitorStatus +from sentry_sdk.crons.decorator import monitor + + +__all__ = [ + "capture_checkin", + "MonitorStatus", + "monitor", +] diff --git a/aws/lambda_demo/sentry_sdk/crons/api.py b/aws/lambda_demo/sentry_sdk/crons/api.py new file mode 100644 index 000000000..20e95685a --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/crons/api.py @@ -0,0 +1,57 @@ +import uuid + +import sentry_sdk + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Optional + from sentry_sdk._types import Event, MonitorConfig + + +def _create_check_in_event( + monitor_slug=None, # type: Optional[str] + check_in_id=None, # type: Optional[str] + status=None, # type: Optional[str] + duration_s=None, # type: Optional[float] + monitor_config=None, # type: Optional[MonitorConfig] +): + # type: (...) -> Event + options = sentry_sdk.get_client().options + check_in_id = check_in_id or uuid.uuid4().hex # type: str + + check_in = { + "type": "check_in", + "monitor_slug": monitor_slug, + "check_in_id": check_in_id, + "status": status, + "duration": duration_s, + "environment": options.get("environment", None), + "release": options.get("release", None), + } # type: Event + + if monitor_config: + check_in["monitor_config"] = monitor_config + + return check_in + + +def capture_checkin( + monitor_slug=None, # type: Optional[str] + check_in_id=None, # type: Optional[str] + status=None, # type: Optional[str] + duration=None, # type: Optional[float] + monitor_config=None, # type: Optional[MonitorConfig] +): + # type: (...) -> str + check_in_event = _create_check_in_event( + monitor_slug=monitor_slug, + check_in_id=check_in_id, + status=status, + duration_s=duration, + monitor_config=monitor_config, + ) + + sentry_sdk.capture_event(check_in_event) + + return check_in_event["check_in_id"] diff --git a/aws/lambda_demo/sentry_sdk/crons/consts.py b/aws/lambda_demo/sentry_sdk/crons/consts.py new file mode 100644 index 000000000..be686b453 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/crons/consts.py @@ -0,0 +1,4 @@ +class MonitorStatus: + IN_PROGRESS = "in_progress" + OK = "ok" + ERROR = "error" diff --git a/aws/lambda_demo/sentry_sdk/crons/decorator.py b/aws/lambda_demo/sentry_sdk/crons/decorator.py new file mode 100644 index 000000000..9af00e61c --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/crons/decorator.py @@ -0,0 +1,135 @@ +from functools import wraps +from inspect import iscoroutinefunction + +from sentry_sdk.crons import capture_checkin +from sentry_sdk.crons.consts import MonitorStatus +from sentry_sdk.utils import now + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Awaitable, Callable + from types import TracebackType + from typing import ( + Any, + Optional, + ParamSpec, + Type, + TypeVar, + Union, + cast, + overload, + ) + from sentry_sdk._types import MonitorConfig + + P = ParamSpec("P") + R = TypeVar("R") + + +class monitor: # noqa: N801 + """ + Decorator/context manager to capture checkin events for a monitor. + + Usage (as decorator): + ``` + import sentry_sdk + + app = Celery() + + @app.task + @sentry_sdk.monitor(monitor_slug='my-fancy-slug') + def test(arg): + print(arg) + ``` + + This does not have to be used with Celery, but if you do use it with celery, + put the `@sentry_sdk.monitor` decorator below Celery's `@app.task` decorator. + + Usage (as context manager): + ``` + import sentry_sdk + + def test(arg): + with sentry_sdk.monitor(monitor_slug='my-fancy-slug'): + print(arg) + ``` + """ + + def __init__(self, monitor_slug=None, monitor_config=None): + # type: (Optional[str], Optional[MonitorConfig]) -> None + self.monitor_slug = monitor_slug + self.monitor_config = monitor_config + + def __enter__(self): + # type: () -> None + self.start_timestamp = now() + self.check_in_id = capture_checkin( + monitor_slug=self.monitor_slug, + status=MonitorStatus.IN_PROGRESS, + monitor_config=self.monitor_config, + ) + + def __exit__(self, exc_type, exc_value, traceback): + # type: (Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]) -> None + duration_s = now() - self.start_timestamp + + if exc_type is None and exc_value is None and traceback is None: + status = MonitorStatus.OK + else: + status = MonitorStatus.ERROR + + capture_checkin( + monitor_slug=self.monitor_slug, + check_in_id=self.check_in_id, + status=status, + duration=duration_s, + monitor_config=self.monitor_config, + ) + + if TYPE_CHECKING: + + @overload + def __call__(self, fn): + # type: (Callable[P, Awaitable[Any]]) -> Callable[P, Awaitable[Any]] + # Unfortunately, mypy does not give us any reliable way to type check the + # return value of an Awaitable (i.e. async function) for this overload, + # since calling iscouroutinefunction narrows the type to Callable[P, Awaitable[Any]]. + ... + + @overload + def __call__(self, fn): + # type: (Callable[P, R]) -> Callable[P, R] + ... + + def __call__( + self, + fn, # type: Union[Callable[P, R], Callable[P, Awaitable[Any]]] + ): + # type: (...) -> Union[Callable[P, R], Callable[P, Awaitable[Any]]] + if iscoroutinefunction(fn): + return self._async_wrapper(fn) + + else: + if TYPE_CHECKING: + fn = cast("Callable[P, R]", fn) + return self._sync_wrapper(fn) + + def _async_wrapper(self, fn): + # type: (Callable[P, Awaitable[Any]]) -> Callable[P, Awaitable[Any]] + @wraps(fn) + async def inner(*args: "P.args", **kwargs: "P.kwargs"): + # type: (...) -> R + with self: + return await fn(*args, **kwargs) + + return inner + + def _sync_wrapper(self, fn): + # type: (Callable[P, R]) -> Callable[P, R] + @wraps(fn) + def inner(*args: "P.args", **kwargs: "P.kwargs"): + # type: (...) -> R + with self: + return fn(*args, **kwargs) + + return inner diff --git a/aws/lambda_demo/sentry_sdk/debug.py b/aws/lambda_demo/sentry_sdk/debug.py new file mode 100644 index 000000000..e4c686a3e --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/debug.py @@ -0,0 +1,41 @@ +import sys +import logging +import warnings + +from sentry_sdk import get_client +from sentry_sdk.client import _client_init_debug +from sentry_sdk.utils import logger +from logging import LogRecord + + +class _DebugFilter(logging.Filter): + def filter(self, record): + # type: (LogRecord) -> bool + if _client_init_debug.get(False): + return True + + return get_client().options["debug"] + + +def init_debug_support(): + # type: () -> None + if not logger.handlers: + configure_logger() + + +def configure_logger(): + # type: () -> None + _handler = logging.StreamHandler(sys.stderr) + _handler.setFormatter(logging.Formatter(" [sentry] %(levelname)s: %(message)s")) + logger.addHandler(_handler) + logger.setLevel(logging.DEBUG) + logger.addFilter(_DebugFilter()) + + +def configure_debug_hub(): + # type: () -> None + warnings.warn( + "configure_debug_hub is deprecated. Please remove calls to it, as it is a no-op.", + DeprecationWarning, + stacklevel=2, + ) diff --git a/aws/lambda_demo/sentry_sdk/envelope.py b/aws/lambda_demo/sentry_sdk/envelope.py new file mode 100644 index 000000000..760116daa --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/envelope.py @@ -0,0 +1,349 @@ +import io +import json +import mimetypes + +from sentry_sdk.session import Session +from sentry_sdk.utils import json_dumps, capture_internal_exceptions + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Optional + from typing import Union + from typing import Dict + from typing import List + from typing import Iterator + + from sentry_sdk._types import Event, EventDataCategory + + +def parse_json(data): + # type: (Union[bytes, str]) -> Any + # on some python 3 versions this needs to be bytes + if isinstance(data, bytes): + data = data.decode("utf-8", "replace") + return json.loads(data) + + +class Envelope: + """ + Represents a Sentry Envelope. The calling code is responsible for adhering to the constraints + documented in the Sentry docs: https://develop.sentry.dev/sdk/envelopes/#data-model. In particular, + each envelope may have at most one Item with type "event" or "transaction" (but not both). + """ + + def __init__( + self, + headers=None, # type: Optional[Dict[str, Any]] + items=None, # type: Optional[List[Item]] + ): + # type: (...) -> None + if headers is not None: + headers = dict(headers) + self.headers = headers or {} + if items is None: + items = [] + else: + items = list(items) + self.items = items + + @property + def description(self): + # type: (...) -> str + return "envelope with %s items (%s)" % ( + len(self.items), + ", ".join(x.data_category for x in self.items), + ) + + def add_event( + self, event # type: Event + ): + # type: (...) -> None + self.add_item(Item(payload=PayloadRef(json=event), type="event")) + + def add_transaction( + self, transaction # type: Event + ): + # type: (...) -> None + self.add_item(Item(payload=PayloadRef(json=transaction), type="transaction")) + + def add_profile( + self, profile # type: Any + ): + # type: (...) -> None + self.add_item(Item(payload=PayloadRef(json=profile), type="profile")) + + def add_profile_chunk( + self, profile_chunk # type: Any + ): + # type: (...) -> None + self.add_item( + Item(payload=PayloadRef(json=profile_chunk), type="profile_chunk") + ) + + def add_checkin( + self, checkin # type: Any + ): + # type: (...) -> None + self.add_item(Item(payload=PayloadRef(json=checkin), type="check_in")) + + def add_session( + self, session # type: Union[Session, Any] + ): + # type: (...) -> None + if isinstance(session, Session): + session = session.to_json() + self.add_item(Item(payload=PayloadRef(json=session), type="session")) + + def add_sessions( + self, sessions # type: Any + ): + # type: (...) -> None + self.add_item(Item(payload=PayloadRef(json=sessions), type="sessions")) + + def add_item( + self, item # type: Item + ): + # type: (...) -> None + self.items.append(item) + + def get_event(self): + # type: (...) -> Optional[Event] + for items in self.items: + event = items.get_event() + if event is not None: + return event + return None + + def get_transaction_event(self): + # type: (...) -> Optional[Event] + for item in self.items: + event = item.get_transaction_event() + if event is not None: + return event + return None + + def __iter__(self): + # type: (...) -> Iterator[Item] + return iter(self.items) + + def serialize_into( + self, f # type: Any + ): + # type: (...) -> None + f.write(json_dumps(self.headers)) + f.write(b"\n") + for item in self.items: + item.serialize_into(f) + + def serialize(self): + # type: (...) -> bytes + out = io.BytesIO() + self.serialize_into(out) + return out.getvalue() + + @classmethod + def deserialize_from( + cls, f # type: Any + ): + # type: (...) -> Envelope + headers = parse_json(f.readline()) + items = [] + while 1: + item = Item.deserialize_from(f) + if item is None: + break + items.append(item) + return cls(headers=headers, items=items) + + @classmethod + def deserialize( + cls, bytes # type: bytes + ): + # type: (...) -> Envelope + return cls.deserialize_from(io.BytesIO(bytes)) + + def __repr__(self): + # type: (...) -> str + return "" % (self.headers, self.items) + + +class PayloadRef: + def __init__( + self, + bytes=None, # type: Optional[bytes] + path=None, # type: Optional[Union[bytes, str]] + json=None, # type: Optional[Any] + ): + # type: (...) -> None + self.json = json + self.bytes = bytes + self.path = path + + def get_bytes(self): + # type: (...) -> bytes + if self.bytes is None: + if self.path is not None: + with capture_internal_exceptions(): + with open(self.path, "rb") as f: + self.bytes = f.read() + elif self.json is not None: + self.bytes = json_dumps(self.json) + return self.bytes or b"" + + @property + def inferred_content_type(self): + # type: (...) -> str + if self.json is not None: + return "application/json" + elif self.path is not None: + path = self.path + if isinstance(path, bytes): + path = path.decode("utf-8", "replace") + ty = mimetypes.guess_type(path)[0] + if ty: + return ty + return "application/octet-stream" + + def __repr__(self): + # type: (...) -> str + return "" % (self.inferred_content_type,) + + +class Item: + def __init__( + self, + payload, # type: Union[bytes, str, PayloadRef] + headers=None, # type: Optional[Dict[str, Any]] + type=None, # type: Optional[str] + content_type=None, # type: Optional[str] + filename=None, # type: Optional[str] + ): + if headers is not None: + headers = dict(headers) + elif headers is None: + headers = {} + self.headers = headers + if isinstance(payload, bytes): + payload = PayloadRef(bytes=payload) + elif isinstance(payload, str): + payload = PayloadRef(bytes=payload.encode("utf-8")) + else: + payload = payload + + if filename is not None: + headers["filename"] = filename + if type is not None: + headers["type"] = type + if content_type is not None: + headers["content_type"] = content_type + elif "content_type" not in headers: + headers["content_type"] = payload.inferred_content_type + + self.payload = payload + + def __repr__(self): + # type: (...) -> str + return "" % ( + self.headers, + self.payload, + self.data_category, + ) + + @property + def type(self): + # type: (...) -> Optional[str] + return self.headers.get("type") + + @property + def data_category(self): + # type: (...) -> EventDataCategory + ty = self.headers.get("type") + if ty == "session" or ty == "sessions": + return "session" + elif ty == "attachment": + return "attachment" + elif ty == "transaction": + return "transaction" + elif ty == "event": + return "error" + elif ty == "client_report": + return "internal" + elif ty == "profile": + return "profile" + elif ty == "profile_chunk": + return "profile_chunk" + elif ty == "statsd": + return "metric_bucket" + elif ty == "check_in": + return "monitor" + else: + return "default" + + def get_bytes(self): + # type: (...) -> bytes + return self.payload.get_bytes() + + def get_event(self): + # type: (...) -> Optional[Event] + """ + Returns an error event if there is one. + """ + if self.type == "event" and self.payload.json is not None: + return self.payload.json + return None + + def get_transaction_event(self): + # type: (...) -> Optional[Event] + if self.type == "transaction" and self.payload.json is not None: + return self.payload.json + return None + + def serialize_into( + self, f # type: Any + ): + # type: (...) -> None + headers = dict(self.headers) + bytes = self.get_bytes() + headers["length"] = len(bytes) + f.write(json_dumps(headers)) + f.write(b"\n") + f.write(bytes) + f.write(b"\n") + + def serialize(self): + # type: (...) -> bytes + out = io.BytesIO() + self.serialize_into(out) + return out.getvalue() + + @classmethod + def deserialize_from( + cls, f # type: Any + ): + # type: (...) -> Optional[Item] + line = f.readline().rstrip() + if not line: + return None + headers = parse_json(line) + length = headers.get("length") + if length is not None: + payload = f.read(length) + f.readline() + else: + # if no length was specified we need to read up to the end of line + # and remove it (if it is present, i.e. not the very last char in an eof terminated envelope) + payload = f.readline().rstrip(b"\n") + if headers.get("type") in ("event", "transaction", "metric_buckets"): + rv = cls(headers=headers, payload=PayloadRef(json=parse_json(payload))) + else: + rv = cls(headers=headers, payload=payload) + return rv + + @classmethod + def deserialize( + cls, bytes # type: bytes + ): + # type: (...) -> Optional[Item] + return cls.deserialize_from(io.BytesIO(bytes)) diff --git a/aws/lambda_demo/sentry_sdk/feature_flags.py b/aws/lambda_demo/sentry_sdk/feature_flags.py new file mode 100644 index 000000000..1187c2fa1 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/feature_flags.py @@ -0,0 +1,42 @@ +import sentry_sdk +from sentry_sdk._lru_cache import LRUCache + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import TypedDict + + FlagData = TypedDict("FlagData", {"flag": str, "result": bool}) + + +DEFAULT_FLAG_CAPACITY = 100 + + +class FlagBuffer: + + def __init__(self, capacity): + # type: (int) -> None + self.buffer = LRUCache(capacity) + self.capacity = capacity + + def clear(self): + # type: () -> None + self.buffer = LRUCache(self.capacity) + + def get(self): + # type: () -> list[FlagData] + return [{"flag": key, "result": value} for key, value in self.buffer.get_all()] + + def set(self, flag, result): + # type: (str, bool) -> None + self.buffer.set(flag, result) + + +def add_feature_flag(flag, result): + # type: (str, bool) -> None + """ + Records a flag and its value to be sent on subsequent error events. + We recommend you do this on flag evaluations. Flags are buffered per Sentry scope. + """ + flags = sentry_sdk.get_current_scope().flags + flags.set(flag, result) diff --git a/aws/lambda_demo/sentry_sdk/hub.py b/aws/lambda_demo/sentry_sdk/hub.py new file mode 100644 index 000000000..7fda9202d --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/hub.py @@ -0,0 +1,739 @@ +import warnings +from contextlib import contextmanager + +from sentry_sdk import ( + get_client, + get_global_scope, + get_isolation_scope, + get_current_scope, +) +from sentry_sdk._compat import with_metaclass +from sentry_sdk.consts import INSTRUMENTER +from sentry_sdk.scope import _ScopeManager +from sentry_sdk.client import Client +from sentry_sdk.tracing import ( + NoOpSpan, + Span, + Transaction, +) + +from sentry_sdk.utils import ( + logger, + ContextVar, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import ContextManager + from typing import Dict + from typing import Generator + from typing import List + from typing import Optional + from typing import overload + from typing import Tuple + from typing import Type + from typing import TypeVar + from typing import Union + + from typing_extensions import Unpack + + from sentry_sdk.scope import Scope + from sentry_sdk.client import BaseClient + from sentry_sdk.integrations import Integration + from sentry_sdk._types import ( + Event, + Hint, + Breadcrumb, + BreadcrumbHint, + ExcInfo, + LogLevelStr, + SamplingContext, + ) + from sentry_sdk.tracing import TransactionKwargs + + T = TypeVar("T") + +else: + + def overload(x): + # type: (T) -> T + return x + + +class SentryHubDeprecationWarning(DeprecationWarning): + """ + A custom deprecation warning to inform users that the Hub is deprecated. + """ + + _MESSAGE = ( + "`sentry_sdk.Hub` is deprecated and will be removed in a future major release. " + "Please consult our 1.x to 2.x migration guide for details on how to migrate " + "`Hub` usage to the new API: " + "https://docs.sentry.io/platforms/python/migration/1.x-to-2.x" + ) + + def __init__(self, *_): + # type: (*object) -> None + super().__init__(self._MESSAGE) + + +@contextmanager +def _suppress_hub_deprecation_warning(): + # type: () -> Generator[None, None, None] + """Utility function to suppress deprecation warnings for the Hub.""" + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=SentryHubDeprecationWarning) + yield + + +_local = ContextVar("sentry_current_hub") + + +class HubMeta(type): + @property + def current(cls): + # type: () -> Hub + """Returns the current instance of the hub.""" + warnings.warn(SentryHubDeprecationWarning(), stacklevel=2) + rv = _local.get(None) + if rv is None: + with _suppress_hub_deprecation_warning(): + # This will raise a deprecation warning; suppress it since we already warned above. + rv = Hub(GLOBAL_HUB) + _local.set(rv) + return rv + + @property + def main(cls): + # type: () -> Hub + """Returns the main instance of the hub.""" + warnings.warn(SentryHubDeprecationWarning(), stacklevel=2) + return GLOBAL_HUB + + +class Hub(with_metaclass(HubMeta)): # type: ignore + """ + .. deprecated:: 2.0.0 + The Hub is deprecated. Its functionality will be merged into :py:class:`sentry_sdk.scope.Scope`. + + The hub wraps the concurrency management of the SDK. Each thread has + its own hub but the hub might transfer with the flow of execution if + context vars are available. + + If the hub is used with a with statement it's temporarily activated. + """ + + _stack = None # type: List[Tuple[Optional[Client], Scope]] + _scope = None # type: Optional[Scope] + + # Mypy doesn't pick up on the metaclass. + + if TYPE_CHECKING: + current = None # type: Hub + main = None # type: Hub + + def __init__( + self, + client_or_hub=None, # type: Optional[Union[Hub, Client]] + scope=None, # type: Optional[Any] + ): + # type: (...) -> None + warnings.warn(SentryHubDeprecationWarning(), stacklevel=2) + + current_scope = None + + if isinstance(client_or_hub, Hub): + client = get_client() + if scope is None: + # hub cloning is going on, we use a fork of the current/isolation scope for context manager + scope = get_isolation_scope().fork() + current_scope = get_current_scope().fork() + else: + client = client_or_hub # type: ignore + get_global_scope().set_client(client) + + if scope is None: # so there is no Hub cloning going on + # just the current isolation scope is used for context manager + scope = get_isolation_scope() + current_scope = get_current_scope() + + if current_scope is None: + # just the current current scope is used for context manager + current_scope = get_current_scope() + + self._stack = [(client, scope)] # type: ignore + self._last_event_id = None # type: Optional[str] + self._old_hubs = [] # type: List[Hub] + + self._old_current_scopes = [] # type: List[Scope] + self._old_isolation_scopes = [] # type: List[Scope] + self._current_scope = current_scope # type: Scope + self._scope = scope # type: Scope + + def __enter__(self): + # type: () -> Hub + self._old_hubs.append(Hub.current) + _local.set(self) + + current_scope = get_current_scope() + self._old_current_scopes.append(current_scope) + scope._current_scope.set(self._current_scope) + + isolation_scope = get_isolation_scope() + self._old_isolation_scopes.append(isolation_scope) + scope._isolation_scope.set(self._scope) + + return self + + def __exit__( + self, + exc_type, # type: Optional[type] + exc_value, # type: Optional[BaseException] + tb, # type: Optional[Any] + ): + # type: (...) -> None + old = self._old_hubs.pop() + _local.set(old) + + old_current_scope = self._old_current_scopes.pop() + scope._current_scope.set(old_current_scope) + + old_isolation_scope = self._old_isolation_scopes.pop() + scope._isolation_scope.set(old_isolation_scope) + + def run( + self, callback # type: Callable[[], T] + ): + # type: (...) -> T + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + + Runs a callback in the context of the hub. Alternatively the + with statement can be used on the hub directly. + """ + with self: + return callback() + + def get_integration( + self, name_or_class # type: Union[str, Type[Integration]] + ): + # type: (...) -> Any + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.client._Client.get_integration` instead. + + Returns the integration for this hub by name or class. If there + is no client bound or the client does not have that integration + then `None` is returned. + + If the return value is not `None` the hub is guaranteed to have a + client attached. + """ + return get_client().get_integration(name_or_class) + + @property + def client(self): + # type: () -> Optional[BaseClient] + """ + .. deprecated:: 2.0.0 + This property is deprecated and will be removed in a future release. + Please use :py:func:`sentry_sdk.api.get_client` instead. + + Returns the current client on the hub. + """ + client = get_client() + + if not client.is_active(): + return None + + return client + + @property + def scope(self): + # type: () -> Scope + """ + .. deprecated:: 2.0.0 + This property is deprecated and will be removed in a future release. + Returns the current scope on the hub. + """ + return get_isolation_scope() + + def last_event_id(self): + # type: () -> Optional[str] + """ + Returns the last event ID. + + .. deprecated:: 1.40.5 + This function is deprecated and will be removed in a future release. The functions `capture_event`, `capture_message`, and `capture_exception` return the event ID directly. + """ + logger.warning( + "Deprecated: last_event_id is deprecated. This will be removed in the future. The functions `capture_event`, `capture_message`, and `capture_exception` return the event ID directly." + ) + return self._last_event_id + + def bind_client( + self, new # type: Optional[BaseClient] + ): + # type: (...) -> None + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.set_client` instead. + + Binds a new client to the hub. + """ + get_global_scope().set_client(new) + + def capture_event(self, event, hint=None, scope=None, **scope_kwargs): + # type: (Event, Optional[Hint], Optional[Scope], Any) -> Optional[str] + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.capture_event` instead. + + Captures an event. + + Alias of :py:meth:`sentry_sdk.Scope.capture_event`. + + :param event: A ready-made event that can be directly sent to Sentry. + + :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object. + + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :param scope_kwargs: Optional data to apply to event. + For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + """ + last_event_id = get_current_scope().capture_event( + event, hint, scope=scope, **scope_kwargs + ) + + is_transaction = event.get("type") == "transaction" + if last_event_id is not None and not is_transaction: + self._last_event_id = last_event_id + + return last_event_id + + def capture_message(self, message, level=None, scope=None, **scope_kwargs): + # type: (str, Optional[LogLevelStr], Optional[Scope], Any) -> Optional[str] + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.capture_message` instead. + + Captures a message. + + Alias of :py:meth:`sentry_sdk.Scope.capture_message`. + + :param message: The string to send as the message to Sentry. + + :param level: If no level is provided, the default level is `info`. + + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :param scope_kwargs: Optional data to apply to event. + For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.client._Client.capture_event`). + """ + last_event_id = get_current_scope().capture_message( + message, level=level, scope=scope, **scope_kwargs + ) + + if last_event_id is not None: + self._last_event_id = last_event_id + + return last_event_id + + def capture_exception(self, error=None, scope=None, **scope_kwargs): + # type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str] + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.capture_exception` instead. + + Captures an exception. + + Alias of :py:meth:`sentry_sdk.Scope.capture_exception`. + + :param error: An exception to capture. If `None`, `sys.exc_info()` will be used. + + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :param scope_kwargs: Optional data to apply to event. + For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.client._Client.capture_event`). + """ + last_event_id = get_current_scope().capture_exception( + error, scope=scope, **scope_kwargs + ) + + if last_event_id is not None: + self._last_event_id = last_event_id + + return last_event_id + + def add_breadcrumb(self, crumb=None, hint=None, **kwargs): + # type: (Optional[Breadcrumb], Optional[BreadcrumbHint], Any) -> None + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.add_breadcrumb` instead. + + Adds a breadcrumb. + + :param crumb: Dictionary with the data as the sentry v7/v8 protocol expects. + + :param hint: An optional value that can be used by `before_breadcrumb` + to customize the breadcrumbs that are emitted. + """ + get_isolation_scope().add_breadcrumb(crumb, hint, **kwargs) + + def start_span(self, instrumenter=INSTRUMENTER.SENTRY, **kwargs): + # type: (str, Any) -> Span + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.start_span` instead. + + Start a span whose parent is the currently active span or transaction, if any. + + The return value is a :py:class:`sentry_sdk.tracing.Span` instance, + typically used as a context manager to start and stop timing in a `with` + block. + + Only spans contained in a transaction are sent to Sentry. Most + integrations start a transaction at the appropriate time, for example + for every incoming HTTP request. Use + :py:meth:`sentry_sdk.start_transaction` to start a new transaction when + one is not already in progress. + + For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Span`. + """ + scope = get_current_scope() + return scope.start_span(instrumenter=instrumenter, **kwargs) + + def start_transaction( + self, + transaction=None, + instrumenter=INSTRUMENTER.SENTRY, + custom_sampling_context=None, + **kwargs + ): + # type: (Optional[Transaction], str, Optional[SamplingContext], Unpack[TransactionKwargs]) -> Union[Transaction, NoOpSpan] + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.start_transaction` instead. + + Start and return a transaction. + + Start an existing transaction if given, otherwise create and start a new + transaction with kwargs. + + This is the entry point to manual tracing instrumentation. + + A tree structure can be built by adding child spans to the transaction, + and child spans to other spans. To start a new child span within the + transaction or any span, call the respective `.start_child()` method. + + Every child span must be finished before the transaction is finished, + otherwise the unfinished spans are discarded. + + When used as context managers, spans and transactions are automatically + finished at the end of the `with` block. If not using context managers, + call the `.finish()` method. + + When the transaction is finished, it will be sent to Sentry with all its + finished child spans. + + For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Transaction`. + """ + scope = get_current_scope() + + # For backwards compatibility, we allow passing the scope as the hub. + # We need a major release to make this nice. (if someone searches the code: deprecated) + # Type checking disabled for this line because deprecated keys are not allowed in the type signature. + kwargs["hub"] = scope # type: ignore + + return scope.start_transaction( + transaction, instrumenter, custom_sampling_context, **kwargs + ) + + def continue_trace(self, environ_or_headers, op=None, name=None, source=None): + # type: (Dict[str, Any], Optional[str], Optional[str], Optional[str]) -> Transaction + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.continue_trace` instead. + + Sets the propagation context from environment or headers and returns a transaction. + """ + return get_isolation_scope().continue_trace( + environ_or_headers=environ_or_headers, op=op, name=name, source=source + ) + + @overload + def push_scope( + self, callback=None # type: Optional[None] + ): + # type: (...) -> ContextManager[Scope] + pass + + @overload + def push_scope( # noqa: F811 + self, callback # type: Callable[[Scope], None] + ): + # type: (...) -> None + pass + + def push_scope( # noqa + self, + callback=None, # type: Optional[Callable[[Scope], None]] + continue_trace=True, # type: bool + ): + # type: (...) -> Optional[ContextManager[Scope]] + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + + Pushes a new layer on the scope stack. + + :param callback: If provided, this method pushes a scope, calls + `callback`, and pops the scope again. + + :returns: If no `callback` is provided, a context manager that should + be used to pop the scope again. + """ + if callback is not None: + with self.push_scope() as scope: + callback(scope) + return None + + return _ScopeManager(self) + + def pop_scope_unsafe(self): + # type: () -> Tuple[Optional[Client], Scope] + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + + Pops a scope layer from the stack. + + Try to use the context manager :py:meth:`push_scope` instead. + """ + rv = self._stack.pop() + assert self._stack, "stack must have at least one layer" + return rv + + @overload + def configure_scope( + self, callback=None # type: Optional[None] + ): + # type: (...) -> ContextManager[Scope] + pass + + @overload + def configure_scope( # noqa: F811 + self, callback # type: Callable[[Scope], None] + ): + # type: (...) -> None + pass + + def configure_scope( # noqa + self, + callback=None, # type: Optional[Callable[[Scope], None]] + continue_trace=True, # type: bool + ): + # type: (...) -> Optional[ContextManager[Scope]] + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + + Reconfigures the scope. + + :param callback: If provided, call the callback with the current scope. + + :returns: If no callback is provided, returns a context manager that returns the scope. + """ + scope = get_isolation_scope() + + if continue_trace: + scope.generate_propagation_context() + + if callback is not None: + # TODO: used to return None when client is None. Check if this changes behavior. + callback(scope) + + return None + + @contextmanager + def inner(): + # type: () -> Generator[Scope, None, None] + yield scope + + return inner() + + def start_session( + self, session_mode="application" # type: str + ): + # type: (...) -> None + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.start_session` instead. + + Starts a new session. + """ + get_isolation_scope().start_session( + session_mode=session_mode, + ) + + def end_session(self): + # type: (...) -> None + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.end_session` instead. + + Ends the current session if there is one. + """ + get_isolation_scope().end_session() + + def stop_auto_session_tracking(self): + # type: (...) -> None + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.stop_auto_session_tracking` instead. + + Stops automatic session tracking. + + This temporarily session tracking for the current scope when called. + To resume session tracking call `resume_auto_session_tracking`. + """ + get_isolation_scope().stop_auto_session_tracking() + + def resume_auto_session_tracking(self): + # type: (...) -> None + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.resume_auto_session_tracking` instead. + + Resumes automatic session tracking for the current scope if + disabled earlier. This requires that generally automatic session + tracking is enabled. + """ + get_isolation_scope().resume_auto_session_tracking() + + def flush( + self, + timeout=None, # type: Optional[float] + callback=None, # type: Optional[Callable[[int, float], None]] + ): + # type: (...) -> None + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.client._Client.flush` instead. + + Alias for :py:meth:`sentry_sdk.client._Client.flush` + """ + return get_client().flush(timeout=timeout, callback=callback) + + def get_traceparent(self): + # type: () -> Optional[str] + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.get_traceparent` instead. + + Returns the traceparent either from the active span or from the scope. + """ + current_scope = get_current_scope() + traceparent = current_scope.get_traceparent() + + if traceparent is None: + isolation_scope = get_isolation_scope() + traceparent = isolation_scope.get_traceparent() + + return traceparent + + def get_baggage(self): + # type: () -> Optional[str] + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.get_baggage` instead. + + Returns Baggage either from the active span or from the scope. + """ + current_scope = get_current_scope() + baggage = current_scope.get_baggage() + + if baggage is None: + isolation_scope = get_isolation_scope() + baggage = isolation_scope.get_baggage() + + if baggage is not None: + return baggage.serialize() + + return None + + def iter_trace_propagation_headers(self, span=None): + # type: (Optional[Span]) -> Generator[Tuple[str, str], None, None] + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.iter_trace_propagation_headers` instead. + + Return HTTP headers which allow propagation of trace data. Data taken + from the span representing the request, if available, or the current + span on the scope if not. + """ + return get_current_scope().iter_trace_propagation_headers( + span=span, + ) + + def trace_propagation_meta(self, span=None): + # type: (Optional[Span]) -> str + """ + .. deprecated:: 2.0.0 + This function is deprecated and will be removed in a future release. + Please use :py:meth:`sentry_sdk.Scope.trace_propagation_meta` instead. + + Return meta tags which should be injected into HTML templates + to allow propagation of trace information. + """ + if span is not None: + logger.warning( + "The parameter `span` in trace_propagation_meta() is deprecated and will be removed in the future." + ) + + return get_current_scope().trace_propagation_meta( + span=span, + ) + + +with _suppress_hub_deprecation_warning(): + # Suppress deprecation warning for the Hub here, since we still always + # import this module. + GLOBAL_HUB = Hub() +_local.set(GLOBAL_HUB) + + +# Circular imports +from sentry_sdk import scope diff --git a/aws/lambda_demo/sentry_sdk/integrations/__init__.py b/aws/lambda_demo/sentry_sdk/integrations/__init__.py new file mode 100644 index 000000000..683382bb9 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/__init__.py @@ -0,0 +1,275 @@ +from abc import ABC, abstractmethod +from threading import Lock + +from sentry_sdk.utils import logger + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Sequence + from typing import Callable + from typing import Dict + from typing import Iterator + from typing import List + from typing import Optional + from typing import Set + from typing import Type + from typing import Union + + +_DEFAULT_FAILED_REQUEST_STATUS_CODES = frozenset(range(500, 600)) + + +_installer_lock = Lock() + +# Set of all integration identifiers we have attempted to install +_processed_integrations = set() # type: Set[str] + +# Set of all integration identifiers we have actually installed +_installed_integrations = set() # type: Set[str] + + +def _generate_default_integrations_iterator( + integrations, # type: List[str] + auto_enabling_integrations, # type: List[str] +): + # type: (...) -> Callable[[bool], Iterator[Type[Integration]]] + + def iter_default_integrations(with_auto_enabling_integrations): + # type: (bool) -> Iterator[Type[Integration]] + """Returns an iterator of the default integration classes:""" + from importlib import import_module + + if with_auto_enabling_integrations: + all_import_strings = integrations + auto_enabling_integrations + else: + all_import_strings = integrations + + for import_string in all_import_strings: + try: + module, cls = import_string.rsplit(".", 1) + yield getattr(import_module(module), cls) + except (DidNotEnable, SyntaxError) as e: + logger.debug( + "Did not import default integration %s: %s", import_string, e + ) + + if isinstance(iter_default_integrations.__doc__, str): + for import_string in integrations: + iter_default_integrations.__doc__ += "\n- `{}`".format(import_string) + + return iter_default_integrations + + +_DEFAULT_INTEGRATIONS = [ + # stdlib/base runtime integrations + "sentry_sdk.integrations.argv.ArgvIntegration", + "sentry_sdk.integrations.atexit.AtexitIntegration", + "sentry_sdk.integrations.dedupe.DedupeIntegration", + "sentry_sdk.integrations.excepthook.ExcepthookIntegration", + "sentry_sdk.integrations.logging.LoggingIntegration", + "sentry_sdk.integrations.modules.ModulesIntegration", + "sentry_sdk.integrations.stdlib.StdlibIntegration", + "sentry_sdk.integrations.threading.ThreadingIntegration", +] + +_AUTO_ENABLING_INTEGRATIONS = [ + "sentry_sdk.integrations.aiohttp.AioHttpIntegration", + "sentry_sdk.integrations.anthropic.AnthropicIntegration", + "sentry_sdk.integrations.ariadne.AriadneIntegration", + "sentry_sdk.integrations.arq.ArqIntegration", + "sentry_sdk.integrations.asyncpg.AsyncPGIntegration", + "sentry_sdk.integrations.boto3.Boto3Integration", + "sentry_sdk.integrations.bottle.BottleIntegration", + "sentry_sdk.integrations.celery.CeleryIntegration", + "sentry_sdk.integrations.chalice.ChaliceIntegration", + "sentry_sdk.integrations.clickhouse_driver.ClickhouseDriverIntegration", + "sentry_sdk.integrations.cohere.CohereIntegration", + "sentry_sdk.integrations.django.DjangoIntegration", + "sentry_sdk.integrations.falcon.FalconIntegration", + "sentry_sdk.integrations.fastapi.FastApiIntegration", + "sentry_sdk.integrations.flask.FlaskIntegration", + "sentry_sdk.integrations.gql.GQLIntegration", + "sentry_sdk.integrations.graphene.GrapheneIntegration", + "sentry_sdk.integrations.httpx.HttpxIntegration", + "sentry_sdk.integrations.huey.HueyIntegration", + "sentry_sdk.integrations.huggingface_hub.HuggingfaceHubIntegration", + "sentry_sdk.integrations.langchain.LangchainIntegration", + "sentry_sdk.integrations.litestar.LitestarIntegration", + "sentry_sdk.integrations.loguru.LoguruIntegration", + "sentry_sdk.integrations.openai.OpenAIIntegration", + "sentry_sdk.integrations.pymongo.PyMongoIntegration", + "sentry_sdk.integrations.pyramid.PyramidIntegration", + "sentry_sdk.integrations.quart.QuartIntegration", + "sentry_sdk.integrations.redis.RedisIntegration", + "sentry_sdk.integrations.rq.RqIntegration", + "sentry_sdk.integrations.sanic.SanicIntegration", + "sentry_sdk.integrations.sqlalchemy.SqlalchemyIntegration", + "sentry_sdk.integrations.starlette.StarletteIntegration", + "sentry_sdk.integrations.starlite.StarliteIntegration", + "sentry_sdk.integrations.strawberry.StrawberryIntegration", + "sentry_sdk.integrations.tornado.TornadoIntegration", +] + +iter_default_integrations = _generate_default_integrations_iterator( + integrations=_DEFAULT_INTEGRATIONS, + auto_enabling_integrations=_AUTO_ENABLING_INTEGRATIONS, +) + +del _generate_default_integrations_iterator + + +_MIN_VERSIONS = { + "aiohttp": (3, 4), + "anthropic": (0, 16), + "ariadne": (0, 20), + "arq": (0, 23), + "asyncpg": (0, 23), + "boto3": (1, 12), # this is actually the botocore version + "bottle": (0, 12), + "celery": (4, 4, 7), + "clickhouse_driver": (0, 2, 0), + "django": (1, 8), + "falcon": (1, 4), + "flask": (0, 10), + "gql": (3, 4, 1), + "graphene": (3, 3), + "ray": (2, 7, 0), + "rq": (0, 6), + "sanic": (0, 8), + "sqlalchemy": (1, 2), + "strawberry": (0, 209, 5), + "tornado": (6, 0), +} + + +def setup_integrations( + integrations, + with_defaults=True, + with_auto_enabling_integrations=False, + disabled_integrations=None, +): + # type: (Sequence[Integration], bool, bool, Optional[Sequence[Union[type[Integration], Integration]]]) -> Dict[str, Integration] + """ + Given a list of integration instances, this installs them all. + + When `with_defaults` is set to `True` all default integrations are added + unless they were already provided before. + + `disabled_integrations` takes precedence over `with_defaults` and + `with_auto_enabling_integrations`. + """ + integrations = dict( + (integration.identifier, integration) for integration in integrations or () + ) + + logger.debug("Setting up integrations (with default = %s)", with_defaults) + + # Integrations that will not be enabled + disabled_integrations = [ + integration if isinstance(integration, type) else type(integration) + for integration in disabled_integrations or [] + ] + + # Integrations that are not explicitly set up by the user. + used_as_default_integration = set() + + if with_defaults: + for integration_cls in iter_default_integrations( + with_auto_enabling_integrations + ): + if integration_cls.identifier not in integrations: + instance = integration_cls() + integrations[instance.identifier] = instance + used_as_default_integration.add(instance.identifier) + + for identifier, integration in integrations.items(): + with _installer_lock: + if identifier not in _processed_integrations: + if type(integration) in disabled_integrations: + logger.debug("Ignoring integration %s", identifier) + else: + logger.debug( + "Setting up previously not enabled integration %s", identifier + ) + try: + type(integration).setup_once() + except DidNotEnable as e: + if identifier not in used_as_default_integration: + raise + + logger.debug( + "Did not enable default integration %s: %s", identifier, e + ) + else: + _installed_integrations.add(identifier) + + _processed_integrations.add(identifier) + + integrations = { + identifier: integration + for identifier, integration in integrations.items() + if identifier in _installed_integrations + } + + for identifier in integrations: + logger.debug("Enabling integration %s", identifier) + + return integrations + + +def _check_minimum_version(integration, version, package=None): + # type: (type[Integration], Optional[tuple[int, ...]], Optional[str]) -> None + package = package or integration.identifier + + if version is None: + raise DidNotEnable(f"Unparsable {package} version.") + + min_version = _MIN_VERSIONS.get(integration.identifier) + if min_version is None: + return + + if version < min_version: + raise DidNotEnable( + f"Integration only supports {package} {'.'.join(map(str, min_version))} or newer." + ) + + +class DidNotEnable(Exception): # noqa: N818 + """ + The integration could not be enabled due to a trivial user error like + `flask` not being installed for the `FlaskIntegration`. + + This exception is silently swallowed for default integrations, but reraised + for explicitly enabled integrations. + """ + + +class Integration(ABC): + """Baseclass for all integrations. + + To accept options for an integration, implement your own constructor that + saves those options on `self`. + """ + + install = None + """Legacy method, do not implement.""" + + identifier = None # type: str + """String unique ID of integration type""" + + @staticmethod + @abstractmethod + def setup_once(): + # type: () -> None + """ + Initialize the integration. + + This function is only called once, ever. Configuration is not available + at this point, so the only thing to do here is to hook into exception + handlers, and perhaps do monkeypatches. + + Inside those hooks `Integration.current` can be used to access the + instance again. + """ + pass diff --git a/aws/lambda_demo/sentry_sdk/integrations/_asgi_common.py b/aws/lambda_demo/sentry_sdk/integrations/_asgi_common.py new file mode 100644 index 000000000..c16bbbcfe --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/_asgi_common.py @@ -0,0 +1,108 @@ +import urllib + +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.integrations._wsgi_common import _filter_headers + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Dict + from typing import Optional + from typing import Union + from typing_extensions import Literal + + from sentry_sdk.utils import AnnotatedValue + + +def _get_headers(asgi_scope): + # type: (Any) -> Dict[str, str] + """ + Extract headers from the ASGI scope, in the format that the Sentry protocol expects. + """ + headers = {} # type: Dict[str, str] + for raw_key, raw_value in asgi_scope["headers"]: + key = raw_key.decode("latin-1") + value = raw_value.decode("latin-1") + if key in headers: + headers[key] = headers[key] + ", " + value + else: + headers[key] = value + + return headers + + +def _get_url(asgi_scope, default_scheme, host): + # type: (Dict[str, Any], Literal["ws", "http"], Optional[Union[AnnotatedValue, str]]) -> str + """ + Extract URL from the ASGI scope, without also including the querystring. + """ + scheme = asgi_scope.get("scheme", default_scheme) + + server = asgi_scope.get("server", None) + path = asgi_scope.get("root_path", "") + asgi_scope.get("path", "") + + if host: + return "%s://%s%s" % (scheme, host, path) + + if server is not None: + host, port = server + default_port = {"http": 80, "https": 443, "ws": 80, "wss": 443}.get(scheme) + if port != default_port: + return "%s://%s:%s%s" % (scheme, host, port, path) + return "%s://%s%s" % (scheme, host, path) + return path + + +def _get_query(asgi_scope): + # type: (Any) -> Any + """ + Extract querystring from the ASGI scope, in the format that the Sentry protocol expects. + """ + qs = asgi_scope.get("query_string") + if not qs: + return None + return urllib.parse.unquote(qs.decode("latin-1")) + + +def _get_ip(asgi_scope): + # type: (Any) -> str + """ + Extract IP Address from the ASGI scope based on request headers with fallback to scope client. + """ + headers = _get_headers(asgi_scope) + try: + return headers["x-forwarded-for"].split(",")[0].strip() + except (KeyError, IndexError): + pass + + try: + return headers["x-real-ip"] + except KeyError: + pass + + return asgi_scope.get("client")[0] + + +def _get_request_data(asgi_scope): + # type: (Any) -> Dict[str, Any] + """ + Returns data related to the HTTP request from the ASGI scope. + """ + request_data = {} # type: Dict[str, Any] + ty = asgi_scope["type"] + if ty in ("http", "websocket"): + request_data["method"] = asgi_scope.get("method") + + request_data["headers"] = headers = _filter_headers(_get_headers(asgi_scope)) + request_data["query_string"] = _get_query(asgi_scope) + + request_data["url"] = _get_url( + asgi_scope, "http" if ty == "http" else "ws", headers.get("host") + ) + + client = asgi_scope.get("client") + if client and should_send_default_pii(): + request_data["env"] = {"REMOTE_ADDR": _get_ip(asgi_scope)} + + return request_data diff --git a/aws/lambda_demo/sentry_sdk/integrations/_wsgi_common.py b/aws/lambda_demo/sentry_sdk/integrations/_wsgi_common.py new file mode 100644 index 000000000..7266a91f5 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/_wsgi_common.py @@ -0,0 +1,264 @@ +from contextlib import contextmanager +import json +from copy import deepcopy + +import sentry_sdk +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.utils import AnnotatedValue, logger + +try: + from django.http.request import RawPostDataException +except ImportError: + RawPostDataException = None + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Dict + from typing import Iterator + from typing import Mapping + from typing import MutableMapping + from typing import Optional + from typing import Union + from sentry_sdk._types import Event, HttpStatusCodeRange + + +SENSITIVE_ENV_KEYS = ( + "REMOTE_ADDR", + "HTTP_X_FORWARDED_FOR", + "HTTP_SET_COOKIE", + "HTTP_COOKIE", + "HTTP_AUTHORIZATION", + "HTTP_X_API_KEY", + "HTTP_X_FORWARDED_FOR", + "HTTP_X_REAL_IP", +) + +SENSITIVE_HEADERS = tuple( + x[len("HTTP_") :] for x in SENSITIVE_ENV_KEYS if x.startswith("HTTP_") +) + +DEFAULT_HTTP_METHODS_TO_CAPTURE = ( + "CONNECT", + "DELETE", + "GET", + # "HEAD", # do not capture HEAD requests by default + # "OPTIONS", # do not capture OPTIONS requests by default + "PATCH", + "POST", + "PUT", + "TRACE", +) + + +# This noop context manager can be replaced with "from contextlib import nullcontext" when we drop Python 3.6 support +@contextmanager +def nullcontext(): + # type: () -> Iterator[None] + yield + + +def request_body_within_bounds(client, content_length): + # type: (Optional[sentry_sdk.client.BaseClient], int) -> bool + if client is None: + return False + + bodies = client.options["max_request_body_size"] + return not ( + bodies == "never" + or (bodies == "small" and content_length > 10**3) + or (bodies == "medium" and content_length > 10**4) + ) + + +class RequestExtractor: + """ + Base class for request extraction. + """ + + # It does not make sense to make this class an ABC because it is not used + # for typing, only so that child classes can inherit common methods from + # it. Only some child classes implement all methods that raise + # NotImplementedError in this class. + + def __init__(self, request): + # type: (Any) -> None + self.request = request + + def extract_into_event(self, event): + # type: (Event) -> None + client = sentry_sdk.get_client() + if not client.is_active(): + return + + data = None # type: Optional[Union[AnnotatedValue, Dict[str, Any]]] + + content_length = self.content_length() + request_info = event.get("request", {}) + + if should_send_default_pii(): + request_info["cookies"] = dict(self.cookies()) + + if not request_body_within_bounds(client, content_length): + data = AnnotatedValue.removed_because_over_size_limit() + else: + # First read the raw body data + # It is important to read this first because if it is Django + # it will cache the body and then we can read the cached version + # again in parsed_body() (or json() or wherever). + raw_data = None + try: + raw_data = self.raw_data() + except (RawPostDataException, ValueError): + # If DjangoRestFramework is used it already read the body for us + # so reading it here will fail. We can ignore this. + pass + + parsed_body = self.parsed_body() + if parsed_body is not None: + data = parsed_body + elif raw_data: + data = AnnotatedValue.removed_because_raw_data() + else: + data = None + + if data is not None: + request_info["data"] = data + + event["request"] = deepcopy(request_info) + + def content_length(self): + # type: () -> int + try: + return int(self.env().get("CONTENT_LENGTH", 0)) + except ValueError: + return 0 + + def cookies(self): + # type: () -> MutableMapping[str, Any] + raise NotImplementedError() + + def raw_data(self): + # type: () -> Optional[Union[str, bytes]] + raise NotImplementedError() + + def form(self): + # type: () -> Optional[Dict[str, Any]] + raise NotImplementedError() + + def parsed_body(self): + # type: () -> Optional[Dict[str, Any]] + form = self.form() + files = self.files() + if form or files: + data = {} + if form: + data = dict(form.items()) + if files: + for key in files.keys(): + data[key] = AnnotatedValue.removed_because_raw_data() + + return data + + return self.json() + + def is_json(self): + # type: () -> bool + return _is_json_content_type(self.env().get("CONTENT_TYPE")) + + def json(self): + # type: () -> Optional[Any] + try: + if not self.is_json(): + return None + + try: + raw_data = self.raw_data() + except (RawPostDataException, ValueError): + # The body might have already been read, in which case this will + # fail + raw_data = None + + if raw_data is None: + return None + + if isinstance(raw_data, str): + return json.loads(raw_data) + else: + return json.loads(raw_data.decode("utf-8")) + except ValueError: + pass + + return None + + def files(self): + # type: () -> Optional[Dict[str, Any]] + raise NotImplementedError() + + def size_of_file(self, file): + # type: (Any) -> int + raise NotImplementedError() + + def env(self): + # type: () -> Dict[str, Any] + raise NotImplementedError() + + +def _is_json_content_type(ct): + # type: (Optional[str]) -> bool + mt = (ct or "").split(";", 1)[0] + return ( + mt == "application/json" + or (mt.startswith("application/")) + and mt.endswith("+json") + ) + + +def _filter_headers(headers): + # type: (Mapping[str, str]) -> Mapping[str, Union[AnnotatedValue, str]] + if should_send_default_pii(): + return headers + + return { + k: ( + v + if k.upper().replace("-", "_") not in SENSITIVE_HEADERS + else AnnotatedValue.removed_because_over_size_limit() + ) + for k, v in headers.items() + } + + +def _in_http_status_code_range(code, code_ranges): + # type: (object, list[HttpStatusCodeRange]) -> bool + for target in code_ranges: + if isinstance(target, int): + if code == target: + return True + continue + + try: + if code in target: + return True + except TypeError: + logger.warning( + "failed_request_status_codes has to be a list of integers or containers" + ) + + return False + + +class HttpCodeRangeContainer: + """ + Wrapper to make it possible to use list[HttpStatusCodeRange] as a Container[int]. + Used for backwards compatibility with the old `failed_request_status_codes` option. + """ + + def __init__(self, code_ranges): + # type: (list[HttpStatusCodeRange]) -> None + self._code_ranges = code_ranges + + def __contains__(self, item): + # type: (object) -> bool + return _in_http_status_code_range(item, self._code_ranges) diff --git a/aws/lambda_demo/sentry_sdk/integrations/aiohttp.py b/aws/lambda_demo/sentry_sdk/integrations/aiohttp.py new file mode 100644 index 000000000..47c1272ae --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/aiohttp.py @@ -0,0 +1,357 @@ +import sys +import weakref +from functools import wraps + +import sentry_sdk +from sentry_sdk.api import continue_trace +from sentry_sdk.consts import OP, SPANSTATUS, SPANDATA +from sentry_sdk.integrations import ( + _DEFAULT_FAILED_REQUEST_STATUS_CODES, + _check_minimum_version, + Integration, + DidNotEnable, +) +from sentry_sdk.integrations.logging import ignore_logger +from sentry_sdk.sessions import track_session +from sentry_sdk.integrations._wsgi_common import ( + _filter_headers, + request_body_within_bounds, +) +from sentry_sdk.tracing import ( + BAGGAGE_HEADER_NAME, + SOURCE_FOR_STYLE, + TRANSACTION_SOURCE_ROUTE, +) +from sentry_sdk.tracing_utils import should_propagate_trace +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + logger, + parse_url, + parse_version, + reraise, + transaction_from_function, + HAS_REAL_CONTEXTVARS, + CONTEXTVARS_ERROR_MESSAGE, + SENSITIVE_DATA_SUBSTITUTE, + AnnotatedValue, +) + +try: + import asyncio + + from aiohttp import __version__ as AIOHTTP_VERSION + from aiohttp import ClientSession, TraceConfig + from aiohttp.web import Application, HTTPException, UrlDispatcher +except ImportError: + raise DidNotEnable("AIOHTTP not installed") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from aiohttp.web_request import Request + from aiohttp.web_urldispatcher import UrlMappingMatchInfo + from aiohttp import TraceRequestStartParams, TraceRequestEndParams + + from collections.abc import Set + from types import SimpleNamespace + from typing import Any + from typing import Optional + from typing import Tuple + from typing import Union + + from sentry_sdk.utils import ExcInfo + from sentry_sdk._types import Event, EventProcessor + + +TRANSACTION_STYLE_VALUES = ("handler_name", "method_and_path_pattern") + + +class AioHttpIntegration(Integration): + identifier = "aiohttp" + origin = f"auto.http.{identifier}" + + def __init__( + self, + transaction_style="handler_name", # type: str + *, + failed_request_status_codes=_DEFAULT_FAILED_REQUEST_STATUS_CODES, # type: Set[int] + ): + # type: (...) -> None + if transaction_style not in TRANSACTION_STYLE_VALUES: + raise ValueError( + "Invalid value for transaction_style: %s (must be in %s)" + % (transaction_style, TRANSACTION_STYLE_VALUES) + ) + self.transaction_style = transaction_style + self._failed_request_status_codes = failed_request_status_codes + + @staticmethod + def setup_once(): + # type: () -> None + + version = parse_version(AIOHTTP_VERSION) + _check_minimum_version(AioHttpIntegration, version) + + if not HAS_REAL_CONTEXTVARS: + # We better have contextvars or we're going to leak state between + # requests. + raise DidNotEnable( + "The aiohttp integration for Sentry requires Python 3.7+ " + " or aiocontextvars package." + CONTEXTVARS_ERROR_MESSAGE + ) + + ignore_logger("aiohttp.server") + + old_handle = Application._handle + + async def sentry_app_handle(self, request, *args, **kwargs): + # type: (Any, Request, *Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(AioHttpIntegration) + if integration is None: + return await old_handle(self, request, *args, **kwargs) + + weak_request = weakref.ref(request) + + with sentry_sdk.isolation_scope() as scope: + with track_session(scope, session_mode="request"): + # Scope data will not leak between requests because aiohttp + # create a task to wrap each request. + scope.generate_propagation_context() + scope.clear_breadcrumbs() + scope.add_event_processor(_make_request_processor(weak_request)) + + headers = dict(request.headers) + transaction = continue_trace( + headers, + op=OP.HTTP_SERVER, + # If this transaction name makes it to the UI, AIOHTTP's + # URL resolver did not find a route or died trying. + name="generic AIOHTTP request", + source=TRANSACTION_SOURCE_ROUTE, + origin=AioHttpIntegration.origin, + ) + with sentry_sdk.start_transaction( + transaction, + custom_sampling_context={"aiohttp_request": request}, + ): + try: + response = await old_handle(self, request) + except HTTPException as e: + transaction.set_http_status(e.status_code) + + if ( + e.status_code + in integration._failed_request_status_codes + ): + _capture_exception() + + raise + except (asyncio.CancelledError, ConnectionResetError): + transaction.set_status(SPANSTATUS.CANCELLED) + raise + except Exception: + # This will probably map to a 500 but seems like we + # have no way to tell. Do not set span status. + reraise(*_capture_exception()) + + try: + # A valid response handler will return a valid response with a status. But, if the handler + # returns an invalid response (e.g. None), the line below will raise an AttributeError. + # Even though this is likely invalid, we need to handle this case to ensure we don't break + # the application. + response_status = response.status + except AttributeError: + pass + else: + transaction.set_http_status(response_status) + + return response + + Application._handle = sentry_app_handle + + old_urldispatcher_resolve = UrlDispatcher.resolve + + @wraps(old_urldispatcher_resolve) + async def sentry_urldispatcher_resolve(self, request): + # type: (UrlDispatcher, Request) -> UrlMappingMatchInfo + rv = await old_urldispatcher_resolve(self, request) + + integration = sentry_sdk.get_client().get_integration(AioHttpIntegration) + if integration is None: + return rv + + name = None + + try: + if integration.transaction_style == "handler_name": + name = transaction_from_function(rv.handler) + elif integration.transaction_style == "method_and_path_pattern": + route_info = rv.get_info() + pattern = route_info.get("path") or route_info.get("formatter") + name = "{} {}".format(request.method, pattern) + except Exception: + pass + + if name is not None: + sentry_sdk.get_current_scope().set_transaction_name( + name, + source=SOURCE_FOR_STYLE[integration.transaction_style], + ) + + return rv + + UrlDispatcher.resolve = sentry_urldispatcher_resolve + + old_client_session_init = ClientSession.__init__ + + @ensure_integration_enabled(AioHttpIntegration, old_client_session_init) + def init(*args, **kwargs): + # type: (Any, Any) -> None + client_trace_configs = list(kwargs.get("trace_configs") or ()) + trace_config = create_trace_config() + client_trace_configs.append(trace_config) + + kwargs["trace_configs"] = client_trace_configs + return old_client_session_init(*args, **kwargs) + + ClientSession.__init__ = init + + +def create_trace_config(): + # type: () -> TraceConfig + + async def on_request_start(session, trace_config_ctx, params): + # type: (ClientSession, SimpleNamespace, TraceRequestStartParams) -> None + if sentry_sdk.get_client().get_integration(AioHttpIntegration) is None: + return + + method = params.method.upper() + + parsed_url = None + with capture_internal_exceptions(): + parsed_url = parse_url(str(params.url), sanitize=False) + + span = sentry_sdk.start_span( + op=OP.HTTP_CLIENT, + name="%s %s" + % (method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE), + origin=AioHttpIntegration.origin, + ) + span.set_data(SPANDATA.HTTP_METHOD, method) + if parsed_url is not None: + span.set_data("url", parsed_url.url) + span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query) + span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment) + + client = sentry_sdk.get_client() + + if should_propagate_trace(client, str(params.url)): + for ( + key, + value, + ) in sentry_sdk.get_current_scope().iter_trace_propagation_headers( + span=span + ): + logger.debug( + "[Tracing] Adding `{key}` header {value} to outgoing request to {url}.".format( + key=key, value=value, url=params.url + ) + ) + if key == BAGGAGE_HEADER_NAME and params.headers.get( + BAGGAGE_HEADER_NAME + ): + # do not overwrite any existing baggage, just append to it + params.headers[key] += "," + value + else: + params.headers[key] = value + + trace_config_ctx.span = span + + async def on_request_end(session, trace_config_ctx, params): + # type: (ClientSession, SimpleNamespace, TraceRequestEndParams) -> None + if trace_config_ctx.span is None: + return + + span = trace_config_ctx.span + span.set_http_status(int(params.response.status)) + span.set_data("reason", params.response.reason) + span.finish() + + trace_config = TraceConfig() + + trace_config.on_request_start.append(on_request_start) + trace_config.on_request_end.append(on_request_end) + + return trace_config + + +def _make_request_processor(weak_request): + # type: (weakref.ReferenceType[Request]) -> EventProcessor + def aiohttp_processor( + event, # type: Event + hint, # type: dict[str, Tuple[type, BaseException, Any]] + ): + # type: (...) -> Event + request = weak_request() + if request is None: + return event + + with capture_internal_exceptions(): + request_info = event.setdefault("request", {}) + + request_info["url"] = "%s://%s%s" % ( + request.scheme, + request.host, + request.path, + ) + + request_info["query_string"] = request.query_string + request_info["method"] = request.method + request_info["env"] = {"REMOTE_ADDR": request.remote} + request_info["headers"] = _filter_headers(dict(request.headers)) + + # Just attach raw data here if it is within bounds, if available. + # Unfortunately there's no way to get structured data from aiohttp + # without awaiting on some coroutine. + request_info["data"] = get_aiohttp_request_data(request) + + return event + + return aiohttp_processor + + +def _capture_exception(): + # type: () -> ExcInfo + exc_info = sys.exc_info() + event, hint = event_from_exception( + exc_info, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "aiohttp", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + return exc_info + + +BODY_NOT_READ_MESSAGE = "[Can't show request body due to implementation details.]" + + +def get_aiohttp_request_data(request): + # type: (Request) -> Union[Optional[str], AnnotatedValue] + bytes_body = request._read_bytes + + if bytes_body is not None: + # we have body to show + if not request_body_within_bounds(sentry_sdk.get_client(), len(bytes_body)): + return AnnotatedValue.removed_because_over_size_limit() + + encoding = request.charset or "utf-8" + return bytes_body.decode(encoding, "replace") + + if request.can_read_body: + # body exists but we can't show it + return BODY_NOT_READ_MESSAGE + + # request has no body + return None diff --git a/aws/lambda_demo/sentry_sdk/integrations/anthropic.py b/aws/lambda_demo/sentry_sdk/integrations/anthropic.py new file mode 100644 index 000000000..f06d8a14d --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/anthropic.py @@ -0,0 +1,286 @@ +from functools import wraps +from typing import TYPE_CHECKING + +import sentry_sdk +from sentry_sdk.ai.monitoring import record_token_usage +from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.utils import ( + capture_internal_exceptions, + event_from_exception, + package_version, +) + +try: + from anthropic.resources import AsyncMessages, Messages + + if TYPE_CHECKING: + from anthropic.types import MessageStreamEvent +except ImportError: + raise DidNotEnable("Anthropic not installed") + +if TYPE_CHECKING: + from typing import Any, AsyncIterator, Iterator + from sentry_sdk.tracing import Span + + +class AnthropicIntegration(Integration): + identifier = "anthropic" + origin = f"auto.ai.{identifier}" + + def __init__(self, include_prompts=True): + # type: (AnthropicIntegration, bool) -> None + self.include_prompts = include_prompts + + @staticmethod + def setup_once(): + # type: () -> None + version = package_version("anthropic") + _check_minimum_version(AnthropicIntegration, version) + + Messages.create = _wrap_message_create(Messages.create) + AsyncMessages.create = _wrap_message_create_async(AsyncMessages.create) + + +def _capture_exception(exc): + # type: (Any) -> None + event, hint = event_from_exception( + exc, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "anthropic", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + +def _calculate_token_usage(result, span): + # type: (Messages, Span) -> None + input_tokens = 0 + output_tokens = 0 + if hasattr(result, "usage"): + usage = result.usage + if hasattr(usage, "input_tokens") and isinstance(usage.input_tokens, int): + input_tokens = usage.input_tokens + if hasattr(usage, "output_tokens") and isinstance(usage.output_tokens, int): + output_tokens = usage.output_tokens + + total_tokens = input_tokens + output_tokens + record_token_usage(span, input_tokens, output_tokens, total_tokens) + + +def _get_responses(content): + # type: (list[Any]) -> list[dict[str, Any]] + """ + Get JSON of a Anthropic responses. + """ + responses = [] + for item in content: + if hasattr(item, "text"): + responses.append( + { + "type": item.type, + "text": item.text, + } + ) + return responses + + +def _collect_ai_data(event, input_tokens, output_tokens, content_blocks): + # type: (MessageStreamEvent, int, int, list[str]) -> tuple[int, int, list[str]] + """ + Count token usage and collect content blocks from the AI streaming response. + """ + with capture_internal_exceptions(): + if hasattr(event, "type"): + if event.type == "message_start": + usage = event.message.usage + input_tokens += usage.input_tokens + output_tokens += usage.output_tokens + elif event.type == "content_block_start": + pass + elif event.type == "content_block_delta": + if hasattr(event.delta, "text"): + content_blocks.append(event.delta.text) + elif event.type == "content_block_stop": + pass + elif event.type == "message_delta": + output_tokens += event.usage.output_tokens + + return input_tokens, output_tokens, content_blocks + + +def _add_ai_data_to_span( + span, integration, input_tokens, output_tokens, content_blocks +): + # type: (Span, AnthropicIntegration, int, int, list[str]) -> None + """ + Add token usage and content blocks from the AI streaming response to the span. + """ + with capture_internal_exceptions(): + if should_send_default_pii() and integration.include_prompts: + complete_message = "".join(content_blocks) + span.set_data( + SPANDATA.AI_RESPONSES, + [{"type": "text", "text": complete_message}], + ) + total_tokens = input_tokens + output_tokens + record_token_usage(span, input_tokens, output_tokens, total_tokens) + span.set_data(SPANDATA.AI_STREAMING, True) + + +def _sentry_patched_create_common(f, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + integration = kwargs.pop("integration") + if integration is None: + return f(*args, **kwargs) + + if "messages" not in kwargs: + return f(*args, **kwargs) + + try: + iter(kwargs["messages"]) + except TypeError: + return f(*args, **kwargs) + + span = sentry_sdk.start_span( + op=OP.ANTHROPIC_MESSAGES_CREATE, + description="Anthropic messages create", + origin=AnthropicIntegration.origin, + ) + span.__enter__() + + result = yield f, args, kwargs + + # add data to span and finish it + messages = list(kwargs["messages"]) + model = kwargs.get("model") + + with capture_internal_exceptions(): + span.set_data(SPANDATA.AI_MODEL_ID, model) + span.set_data(SPANDATA.AI_STREAMING, False) + + if should_send_default_pii() and integration.include_prompts: + span.set_data(SPANDATA.AI_INPUT_MESSAGES, messages) + + if hasattr(result, "content"): + if should_send_default_pii() and integration.include_prompts: + span.set_data(SPANDATA.AI_RESPONSES, _get_responses(result.content)) + _calculate_token_usage(result, span) + span.__exit__(None, None, None) + + # Streaming response + elif hasattr(result, "_iterator"): + old_iterator = result._iterator + + def new_iterator(): + # type: () -> Iterator[MessageStreamEvent] + input_tokens = 0 + output_tokens = 0 + content_blocks = [] # type: list[str] + + for event in old_iterator: + input_tokens, output_tokens, content_blocks = _collect_ai_data( + event, input_tokens, output_tokens, content_blocks + ) + if event.type != "message_stop": + yield event + + _add_ai_data_to_span( + span, integration, input_tokens, output_tokens, content_blocks + ) + span.__exit__(None, None, None) + + async def new_iterator_async(): + # type: () -> AsyncIterator[MessageStreamEvent] + input_tokens = 0 + output_tokens = 0 + content_blocks = [] # type: list[str] + + async for event in old_iterator: + input_tokens, output_tokens, content_blocks = _collect_ai_data( + event, input_tokens, output_tokens, content_blocks + ) + if event.type != "message_stop": + yield event + + _add_ai_data_to_span( + span, integration, input_tokens, output_tokens, content_blocks + ) + span.__exit__(None, None, None) + + if str(type(result._iterator)) == "": + result._iterator = new_iterator_async() + else: + result._iterator = new_iterator() + + else: + span.set_data("unknown_response", True) + span.__exit__(None, None, None) + + return result + + +def _wrap_message_create(f): + # type: (Any) -> Any + def _execute_sync(f, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + gen = _sentry_patched_create_common(f, *args, **kwargs) + + try: + f, args, kwargs = next(gen) + except StopIteration as e: + return e.value + + try: + try: + result = f(*args, **kwargs) + except Exception as exc: + _capture_exception(exc) + raise exc from None + + return gen.send(result) + except StopIteration as e: + return e.value + + @wraps(f) + def _sentry_patched_create_sync(*args, **kwargs): + # type: (*Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(AnthropicIntegration) + kwargs["integration"] = integration + + return _execute_sync(f, *args, **kwargs) + + return _sentry_patched_create_sync + + +def _wrap_message_create_async(f): + # type: (Any) -> Any + async def _execute_async(f, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + gen = _sentry_patched_create_common(f, *args, **kwargs) + + try: + f, args, kwargs = next(gen) + except StopIteration as e: + return await e.value + + try: + try: + result = await f(*args, **kwargs) + except Exception as exc: + _capture_exception(exc) + raise exc from None + + return gen.send(result) + except StopIteration as e: + return e.value + + @wraps(f) + async def _sentry_patched_create_async(*args, **kwargs): + # type: (*Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(AnthropicIntegration) + kwargs["integration"] = integration + + return await _execute_async(f, *args, **kwargs) + + return _sentry_patched_create_async diff --git a/aws/lambda_demo/sentry_sdk/integrations/argv.py b/aws/lambda_demo/sentry_sdk/integrations/argv.py new file mode 100644 index 000000000..315feefb4 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/argv.py @@ -0,0 +1,31 @@ +import sys + +import sentry_sdk +from sentry_sdk.integrations import Integration +from sentry_sdk.scope import add_global_event_processor + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Optional + + from sentry_sdk._types import Event, Hint + + +class ArgvIntegration(Integration): + identifier = "argv" + + @staticmethod + def setup_once(): + # type: () -> None + @add_global_event_processor + def processor(event, hint): + # type: (Event, Optional[Hint]) -> Optional[Event] + if sentry_sdk.get_client().get_integration(ArgvIntegration) is not None: + extra = event.setdefault("extra", {}) + # If some event processor decided to set extra to e.g. an + # `int`, don't crash. Not here. + if isinstance(extra, dict): + extra["sys.argv"] = sys.argv + + return event diff --git a/aws/lambda_demo/sentry_sdk/integrations/ariadne.py b/aws/lambda_demo/sentry_sdk/integrations/ariadne.py new file mode 100644 index 000000000..033614044 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/ariadne.py @@ -0,0 +1,161 @@ +from importlib import import_module + +import sentry_sdk +from sentry_sdk import get_client, capture_event +from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration +from sentry_sdk.integrations.logging import ignore_logger +from sentry_sdk.integrations._wsgi_common import request_body_within_bounds +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + package_version, +) + +try: + # importing like this is necessary due to name shadowing in ariadne + # (ariadne.graphql is also a function) + ariadne_graphql = import_module("ariadne.graphql") +except ImportError: + raise DidNotEnable("ariadne is not installed") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Dict, List, Optional + from ariadne.types import GraphQLError, GraphQLResult, GraphQLSchema, QueryParser # type: ignore + from graphql.language.ast import DocumentNode # type: ignore + from sentry_sdk._types import Event, EventProcessor + + +class AriadneIntegration(Integration): + identifier = "ariadne" + + @staticmethod + def setup_once(): + # type: () -> None + version = package_version("ariadne") + _check_minimum_version(AriadneIntegration, version) + + ignore_logger("ariadne") + + _patch_graphql() + + +def _patch_graphql(): + # type: () -> None + old_parse_query = ariadne_graphql.parse_query + old_handle_errors = ariadne_graphql.handle_graphql_errors + old_handle_query_result = ariadne_graphql.handle_query_result + + @ensure_integration_enabled(AriadneIntegration, old_parse_query) + def _sentry_patched_parse_query(context_value, query_parser, data): + # type: (Optional[Any], Optional[QueryParser], Any) -> DocumentNode + event_processor = _make_request_event_processor(data) + sentry_sdk.get_isolation_scope().add_event_processor(event_processor) + + result = old_parse_query(context_value, query_parser, data) + return result + + @ensure_integration_enabled(AriadneIntegration, old_handle_errors) + def _sentry_patched_handle_graphql_errors(errors, *args, **kwargs): + # type: (List[GraphQLError], Any, Any) -> GraphQLResult + result = old_handle_errors(errors, *args, **kwargs) + + event_processor = _make_response_event_processor(result[1]) + sentry_sdk.get_isolation_scope().add_event_processor(event_processor) + + client = get_client() + if client.is_active(): + with capture_internal_exceptions(): + for error in errors: + event, hint = event_from_exception( + error, + client_options=client.options, + mechanism={ + "type": AriadneIntegration.identifier, + "handled": False, + }, + ) + capture_event(event, hint=hint) + + return result + + @ensure_integration_enabled(AriadneIntegration, old_handle_query_result) + def _sentry_patched_handle_query_result(result, *args, **kwargs): + # type: (Any, Any, Any) -> GraphQLResult + query_result = old_handle_query_result(result, *args, **kwargs) + + event_processor = _make_response_event_processor(query_result[1]) + sentry_sdk.get_isolation_scope().add_event_processor(event_processor) + + client = get_client() + if client.is_active(): + with capture_internal_exceptions(): + for error in result.errors or []: + event, hint = event_from_exception( + error, + client_options=client.options, + mechanism={ + "type": AriadneIntegration.identifier, + "handled": False, + }, + ) + capture_event(event, hint=hint) + + return query_result + + ariadne_graphql.parse_query = _sentry_patched_parse_query # type: ignore + ariadne_graphql.handle_graphql_errors = _sentry_patched_handle_graphql_errors # type: ignore + ariadne_graphql.handle_query_result = _sentry_patched_handle_query_result # type: ignore + + +def _make_request_event_processor(data): + # type: (GraphQLSchema) -> EventProcessor + """Add request data and api_target to events.""" + + def inner(event, hint): + # type: (Event, dict[str, Any]) -> Event + if not isinstance(data, dict): + return event + + with capture_internal_exceptions(): + try: + content_length = int( + (data.get("headers") or {}).get("Content-Length", 0) + ) + except (TypeError, ValueError): + return event + + if should_send_default_pii() and request_body_within_bounds( + get_client(), content_length + ): + request_info = event.setdefault("request", {}) + request_info["api_target"] = "graphql" + request_info["data"] = data + + elif event.get("request", {}).get("data"): + del event["request"]["data"] + + return event + + return inner + + +def _make_response_event_processor(response): + # type: (Dict[str, Any]) -> EventProcessor + """Add response data to the event's response context.""" + + def inner(event, hint): + # type: (Event, dict[str, Any]) -> Event + with capture_internal_exceptions(): + if should_send_default_pii() and response.get("errors"): + contexts = event.setdefault("contexts", {}) + contexts["response"] = { + "data": response, + } + + return event + + return inner diff --git a/aws/lambda_demo/sentry_sdk/integrations/arq.py b/aws/lambda_demo/sentry_sdk/integrations/arq.py new file mode 100644 index 000000000..a2cce8e0f --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/arq.py @@ -0,0 +1,245 @@ +import sys + +import sentry_sdk +from sentry_sdk.consts import OP, SPANSTATUS +from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration +from sentry_sdk.integrations.logging import ignore_logger +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import Transaction, TRANSACTION_SOURCE_TASK +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + SENSITIVE_DATA_SUBSTITUTE, + parse_version, + reraise, +) + +try: + import arq.worker + from arq.version import VERSION as ARQ_VERSION + from arq.connections import ArqRedis + from arq.worker import JobExecutionFailed, Retry, RetryJob, Worker +except ImportError: + raise DidNotEnable("Arq is not installed") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Dict, Optional, Union + + from sentry_sdk._types import EventProcessor, Event, ExcInfo, Hint + + from arq.cron import CronJob + from arq.jobs import Job + from arq.typing import WorkerCoroutine + from arq.worker import Function + +ARQ_CONTROL_FLOW_EXCEPTIONS = (JobExecutionFailed, Retry, RetryJob) + + +class ArqIntegration(Integration): + identifier = "arq" + origin = f"auto.queue.{identifier}" + + @staticmethod + def setup_once(): + # type: () -> None + + try: + if isinstance(ARQ_VERSION, str): + version = parse_version(ARQ_VERSION) + else: + version = ARQ_VERSION.version[:2] + + except (TypeError, ValueError): + version = None + + _check_minimum_version(ArqIntegration, version) + + patch_enqueue_job() + patch_run_job() + patch_create_worker() + + ignore_logger("arq.worker") + + +def patch_enqueue_job(): + # type: () -> None + old_enqueue_job = ArqRedis.enqueue_job + original_kwdefaults = old_enqueue_job.__kwdefaults__ + + async def _sentry_enqueue_job(self, function, *args, **kwargs): + # type: (ArqRedis, str, *Any, **Any) -> Optional[Job] + integration = sentry_sdk.get_client().get_integration(ArqIntegration) + if integration is None: + return await old_enqueue_job(self, function, *args, **kwargs) + + with sentry_sdk.start_span( + op=OP.QUEUE_SUBMIT_ARQ, name=function, origin=ArqIntegration.origin + ): + return await old_enqueue_job(self, function, *args, **kwargs) + + _sentry_enqueue_job.__kwdefaults__ = original_kwdefaults + ArqRedis.enqueue_job = _sentry_enqueue_job + + +def patch_run_job(): + # type: () -> None + old_run_job = Worker.run_job + + async def _sentry_run_job(self, job_id, score): + # type: (Worker, str, int) -> None + integration = sentry_sdk.get_client().get_integration(ArqIntegration) + if integration is None: + return await old_run_job(self, job_id, score) + + with sentry_sdk.isolation_scope() as scope: + scope._name = "arq" + scope.clear_breadcrumbs() + + transaction = Transaction( + name="unknown arq task", + status="ok", + op=OP.QUEUE_TASK_ARQ, + source=TRANSACTION_SOURCE_TASK, + origin=ArqIntegration.origin, + ) + + with sentry_sdk.start_transaction(transaction): + return await old_run_job(self, job_id, score) + + Worker.run_job = _sentry_run_job + + +def _capture_exception(exc_info): + # type: (ExcInfo) -> None + scope = sentry_sdk.get_current_scope() + + if scope.transaction is not None: + if exc_info[0] in ARQ_CONTROL_FLOW_EXCEPTIONS: + scope.transaction.set_status(SPANSTATUS.ABORTED) + return + + scope.transaction.set_status(SPANSTATUS.INTERNAL_ERROR) + + event, hint = event_from_exception( + exc_info, + client_options=sentry_sdk.get_client().options, + mechanism={"type": ArqIntegration.identifier, "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + +def _make_event_processor(ctx, *args, **kwargs): + # type: (Dict[Any, Any], *Any, **Any) -> EventProcessor + def event_processor(event, hint): + # type: (Event, Hint) -> Optional[Event] + + with capture_internal_exceptions(): + scope = sentry_sdk.get_current_scope() + if scope.transaction is not None: + scope.transaction.name = ctx["job_name"] + event["transaction"] = ctx["job_name"] + + tags = event.setdefault("tags", {}) + tags["arq_task_id"] = ctx["job_id"] + tags["arq_task_retry"] = ctx["job_try"] > 1 + extra = event.setdefault("extra", {}) + extra["arq-job"] = { + "task": ctx["job_name"], + "args": ( + args if should_send_default_pii() else SENSITIVE_DATA_SUBSTITUTE + ), + "kwargs": ( + kwargs if should_send_default_pii() else SENSITIVE_DATA_SUBSTITUTE + ), + "retry": ctx["job_try"], + } + + return event + + return event_processor + + +def _wrap_coroutine(name, coroutine): + # type: (str, WorkerCoroutine) -> WorkerCoroutine + + async def _sentry_coroutine(ctx, *args, **kwargs): + # type: (Dict[Any, Any], *Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(ArqIntegration) + if integration is None: + return await coroutine(ctx, *args, **kwargs) + + sentry_sdk.get_isolation_scope().add_event_processor( + _make_event_processor({**ctx, "job_name": name}, *args, **kwargs) + ) + + try: + result = await coroutine(ctx, *args, **kwargs) + except Exception: + exc_info = sys.exc_info() + _capture_exception(exc_info) + reraise(*exc_info) + + return result + + return _sentry_coroutine + + +def patch_create_worker(): + # type: () -> None + old_create_worker = arq.worker.create_worker + + @ensure_integration_enabled(ArqIntegration, old_create_worker) + def _sentry_create_worker(*args, **kwargs): + # type: (*Any, **Any) -> Worker + settings_cls = args[0] + + if isinstance(settings_cls, dict): + if "functions" in settings_cls: + settings_cls["functions"] = [ + _get_arq_function(func) for func in settings_cls["functions"] + ] + if "cron_jobs" in settings_cls: + settings_cls["cron_jobs"] = [ + _get_arq_cron_job(cron_job) + for cron_job in settings_cls["cron_jobs"] + ] + + if hasattr(settings_cls, "functions"): + settings_cls.functions = [ + _get_arq_function(func) for func in settings_cls.functions + ] + if hasattr(settings_cls, "cron_jobs"): + settings_cls.cron_jobs = [ + _get_arq_cron_job(cron_job) for cron_job in settings_cls.cron_jobs + ] + + if "functions" in kwargs: + kwargs["functions"] = [ + _get_arq_function(func) for func in kwargs["functions"] + ] + if "cron_jobs" in kwargs: + kwargs["cron_jobs"] = [ + _get_arq_cron_job(cron_job) for cron_job in kwargs["cron_jobs"] + ] + + return old_create_worker(*args, **kwargs) + + arq.worker.create_worker = _sentry_create_worker + + +def _get_arq_function(func): + # type: (Union[str, Function, WorkerCoroutine]) -> Function + arq_func = arq.worker.func(func) + arq_func.coroutine = _wrap_coroutine(arq_func.name, arq_func.coroutine) + + return arq_func + + +def _get_arq_cron_job(cron_job): + # type: (CronJob) -> CronJob + cron_job.coroutine = _wrap_coroutine(cron_job.name, cron_job.coroutine) + + return cron_job diff --git a/aws/lambda_demo/sentry_sdk/integrations/asgi.py b/aws/lambda_demo/sentry_sdk/integrations/asgi.py new file mode 100644 index 000000000..f5e8665b4 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/asgi.py @@ -0,0 +1,335 @@ +""" +An ASGI middleware. + +Based on Tom Christie's `sentry-asgi `. +""" + +import asyncio +import inspect +from copy import deepcopy +from functools import partial + +import sentry_sdk +from sentry_sdk.api import continue_trace +from sentry_sdk.consts import OP + +from sentry_sdk.integrations._asgi_common import ( + _get_headers, + _get_request_data, + _get_url, +) +from sentry_sdk.integrations._wsgi_common import ( + DEFAULT_HTTP_METHODS_TO_CAPTURE, + nullcontext, +) +from sentry_sdk.sessions import track_session +from sentry_sdk.tracing import ( + SOURCE_FOR_STYLE, + TRANSACTION_SOURCE_ROUTE, + TRANSACTION_SOURCE_URL, + TRANSACTION_SOURCE_COMPONENT, + TRANSACTION_SOURCE_CUSTOM, +) +from sentry_sdk.utils import ( + ContextVar, + event_from_exception, + HAS_REAL_CONTEXTVARS, + CONTEXTVARS_ERROR_MESSAGE, + logger, + transaction_from_function, + _get_installed_modules, +) +from sentry_sdk.tracing import Transaction + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import Dict + from typing import Optional + from typing import Tuple + + from sentry_sdk._types import Event, Hint + + +_asgi_middleware_applied = ContextVar("sentry_asgi_middleware_applied") + +_DEFAULT_TRANSACTION_NAME = "generic ASGI request" + +TRANSACTION_STYLE_VALUES = ("endpoint", "url") + + +def _capture_exception(exc, mechanism_type="asgi"): + # type: (Any, str) -> None + + event, hint = event_from_exception( + exc, + client_options=sentry_sdk.get_client().options, + mechanism={"type": mechanism_type, "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + +def _looks_like_asgi3(app): + # type: (Any) -> bool + """ + Try to figure out if an application object supports ASGI3. + + This is how uvicorn figures out the application version as well. + """ + if inspect.isclass(app): + return hasattr(app, "__await__") + elif inspect.isfunction(app): + return asyncio.iscoroutinefunction(app) + else: + call = getattr(app, "__call__", None) # noqa + return asyncio.iscoroutinefunction(call) + + +class SentryAsgiMiddleware: + __slots__ = ( + "app", + "__call__", + "transaction_style", + "mechanism_type", + "span_origin", + "http_methods_to_capture", + ) + + def __init__( + self, + app, # type: Any + unsafe_context_data=False, # type: bool + transaction_style="endpoint", # type: str + mechanism_type="asgi", # type: str + span_origin="manual", # type: str + http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: Tuple[str, ...] + ): + # type: (...) -> None + """ + Instrument an ASGI application with Sentry. Provides HTTP/websocket + data to sent events and basic handling for exceptions bubbling up + through the middleware. + + :param unsafe_context_data: Disable errors when a proper contextvars installation could not be found. We do not recommend changing this from the default. + """ + if not unsafe_context_data and not HAS_REAL_CONTEXTVARS: + # We better have contextvars or we're going to leak state between + # requests. + raise RuntimeError( + "The ASGI middleware for Sentry requires Python 3.7+ " + "or the aiocontextvars package." + CONTEXTVARS_ERROR_MESSAGE + ) + if transaction_style not in TRANSACTION_STYLE_VALUES: + raise ValueError( + "Invalid value for transaction_style: %s (must be in %s)" + % (transaction_style, TRANSACTION_STYLE_VALUES) + ) + + asgi_middleware_while_using_starlette_or_fastapi = ( + mechanism_type == "asgi" and "starlette" in _get_installed_modules() + ) + if asgi_middleware_while_using_starlette_or_fastapi: + logger.warning( + "The Sentry Python SDK can now automatically support ASGI frameworks like Starlette and FastAPI. " + "Please remove 'SentryAsgiMiddleware' from your project. " + "See https://docs.sentry.io/platforms/python/guides/asgi/ for more information." + ) + + self.transaction_style = transaction_style + self.mechanism_type = mechanism_type + self.span_origin = span_origin + self.app = app + self.http_methods_to_capture = http_methods_to_capture + + if _looks_like_asgi3(app): + self.__call__ = self._run_asgi3 # type: Callable[..., Any] + else: + self.__call__ = self._run_asgi2 + + def _run_asgi2(self, scope): + # type: (Any) -> Any + async def inner(receive, send): + # type: (Any, Any) -> Any + return await self._run_app(scope, receive, send, asgi_version=2) + + return inner + + async def _run_asgi3(self, scope, receive, send): + # type: (Any, Any, Any) -> Any + return await self._run_app(scope, receive, send, asgi_version=3) + + async def _run_app(self, scope, receive, send, asgi_version): + # type: (Any, Any, Any, Any, int) -> Any + is_recursive_asgi_middleware = _asgi_middleware_applied.get(False) + is_lifespan = scope["type"] == "lifespan" + if is_recursive_asgi_middleware or is_lifespan: + try: + if asgi_version == 2: + return await self.app(scope)(receive, send) + else: + return await self.app(scope, receive, send) + + except Exception as exc: + _capture_exception(exc, mechanism_type=self.mechanism_type) + raise exc from None + + _asgi_middleware_applied.set(True) + try: + with sentry_sdk.isolation_scope() as sentry_scope: + with track_session(sentry_scope, session_mode="request"): + sentry_scope.clear_breadcrumbs() + sentry_scope._name = "asgi" + processor = partial(self.event_processor, asgi_scope=scope) + sentry_scope.add_event_processor(processor) + + ty = scope["type"] + ( + transaction_name, + transaction_source, + ) = self._get_transaction_name_and_source( + self.transaction_style, + scope, + ) + + method = scope.get("method", "").upper() + transaction = None + if method in self.http_methods_to_capture: + if ty in ("http", "websocket"): + transaction = continue_trace( + _get_headers(scope), + op="{}.server".format(ty), + name=transaction_name, + source=transaction_source, + origin=self.span_origin, + ) + logger.debug( + "[ASGI] Created transaction (continuing trace): %s", + transaction, + ) + else: + transaction = Transaction( + op=OP.HTTP_SERVER, + name=transaction_name, + source=transaction_source, + origin=self.span_origin, + ) + logger.debug( + "[ASGI] Created transaction (new): %s", transaction + ) + + transaction.set_tag("asgi.type", ty) + logger.debug( + "[ASGI] Set transaction name and source on transaction: '%s' / '%s'", + transaction.name, + transaction.source, + ) + + with ( + sentry_sdk.start_transaction( + transaction, + custom_sampling_context={"asgi_scope": scope}, + ) + if transaction is not None + else nullcontext() + ): + logger.debug("[ASGI] Started transaction: %s", transaction) + try: + + async def _sentry_wrapped_send(event): + # type: (Dict[str, Any]) -> Any + if transaction is not None: + is_http_response = ( + event.get("type") == "http.response.start" + and "status" in event + ) + if is_http_response: + transaction.set_http_status(event["status"]) + + return await send(event) + + if asgi_version == 2: + return await self.app(scope)( + receive, _sentry_wrapped_send + ) + else: + return await self.app( + scope, receive, _sentry_wrapped_send + ) + except Exception as exc: + _capture_exception(exc, mechanism_type=self.mechanism_type) + raise exc from None + finally: + _asgi_middleware_applied.set(False) + + def event_processor(self, event, hint, asgi_scope): + # type: (Event, Hint, Any) -> Optional[Event] + request_data = event.get("request", {}) + request_data.update(_get_request_data(asgi_scope)) + event["request"] = deepcopy(request_data) + + # Only set transaction name if not already set by Starlette or FastAPI (or other frameworks) + already_set = event["transaction"] != _DEFAULT_TRANSACTION_NAME and event[ + "transaction_info" + ].get("source") in [ + TRANSACTION_SOURCE_COMPONENT, + TRANSACTION_SOURCE_ROUTE, + TRANSACTION_SOURCE_CUSTOM, + ] + if not already_set: + name, source = self._get_transaction_name_and_source( + self.transaction_style, asgi_scope + ) + event["transaction"] = name + event["transaction_info"] = {"source": source} + + logger.debug( + "[ASGI] Set transaction name and source in event_processor: '%s' / '%s'", + event["transaction"], + event["transaction_info"]["source"], + ) + + return event + + # Helper functions. + # + # Note: Those functions are not public API. If you want to mutate request + # data to your liking it's recommended to use the `before_send` callback + # for that. + + def _get_transaction_name_and_source(self, transaction_style, asgi_scope): + # type: (SentryAsgiMiddleware, str, Any) -> Tuple[str, str] + name = None + source = SOURCE_FOR_STYLE[transaction_style] + ty = asgi_scope.get("type") + + if transaction_style == "endpoint": + endpoint = asgi_scope.get("endpoint") + # Webframeworks like Starlette mutate the ASGI env once routing is + # done, which is sometime after the request has started. If we have + # an endpoint, overwrite our generic transaction name. + if endpoint: + name = transaction_from_function(endpoint) or "" + else: + name = _get_url(asgi_scope, "http" if ty == "http" else "ws", host=None) + source = TRANSACTION_SOURCE_URL + + elif transaction_style == "url": + # FastAPI includes the route object in the scope to let Sentry extract the + # path from it for the transaction name + route = asgi_scope.get("route") + if route: + path = getattr(route, "path", None) + if path is not None: + name = path + else: + name = _get_url(asgi_scope, "http" if ty == "http" else "ws", host=None) + source = TRANSACTION_SOURCE_URL + + if name is None: + name = _DEFAULT_TRANSACTION_NAME + source = TRANSACTION_SOURCE_ROUTE + return name, source + + return name, source diff --git a/aws/lambda_demo/sentry_sdk/integrations/asyncio.py b/aws/lambda_demo/sentry_sdk/integrations/asyncio.py new file mode 100644 index 000000000..7021d7fce --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/asyncio.py @@ -0,0 +1,107 @@ +import sys + +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.integrations import Integration, DidNotEnable +from sentry_sdk.utils import event_from_exception, reraise + +try: + import asyncio + from asyncio.tasks import Task +except ImportError: + raise DidNotEnable("asyncio not available") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from collections.abc import Coroutine + + from sentry_sdk._types import ExcInfo + + +def get_name(coro): + # type: (Any) -> str + return ( + getattr(coro, "__qualname__", None) + or getattr(coro, "__name__", None) + or "coroutine without __name__" + ) + + +def patch_asyncio(): + # type: () -> None + orig_task_factory = None + try: + loop = asyncio.get_running_loop() + orig_task_factory = loop.get_task_factory() + + def _sentry_task_factory(loop, coro, **kwargs): + # type: (asyncio.AbstractEventLoop, Coroutine[Any, Any, Any], Any) -> asyncio.Future[Any] + + async def _coro_creating_hub_and_span(): + # type: () -> Any + result = None + + with sentry_sdk.isolation_scope(): + with sentry_sdk.start_span( + op=OP.FUNCTION, + name=get_name(coro), + origin=AsyncioIntegration.origin, + ): + try: + result = await coro + except Exception: + reraise(*_capture_exception()) + + return result + + # Trying to use user set task factory (if there is one) + if orig_task_factory: + return orig_task_factory(loop, _coro_creating_hub_and_span(), **kwargs) + + # The default task factory in `asyncio` does not have its own function + # but is just a couple of lines in `asyncio.base_events.create_task()` + # Those lines are copied here. + + # WARNING: + # If the default behavior of the task creation in asyncio changes, + # this will break! + task = Task(_coro_creating_hub_and_span(), loop=loop, **kwargs) + if task._source_traceback: # type: ignore + del task._source_traceback[-1] # type: ignore + + return task + + loop.set_task_factory(_sentry_task_factory) # type: ignore + except RuntimeError: + # When there is no running loop, we have nothing to patch. + pass + + +def _capture_exception(): + # type: () -> ExcInfo + exc_info = sys.exc_info() + + client = sentry_sdk.get_client() + + integration = client.get_integration(AsyncioIntegration) + if integration is not None: + event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "asyncio", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + return exc_info + + +class AsyncioIntegration(Integration): + identifier = "asyncio" + origin = f"auto.function.{identifier}" + + @staticmethod + def setup_once(): + # type: () -> None + patch_asyncio() diff --git a/aws/lambda_demo/sentry_sdk/integrations/asyncpg.py b/aws/lambda_demo/sentry_sdk/integrations/asyncpg.py new file mode 100644 index 000000000..b6b53f466 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/asyncpg.py @@ -0,0 +1,208 @@ +from __future__ import annotations +import contextlib +from typing import Any, TypeVar, Callable, Awaitable, Iterator + +import sentry_sdk +from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable +from sentry_sdk.tracing import Span +from sentry_sdk.tracing_utils import add_query_source, record_sql_queries +from sentry_sdk.utils import ( + ensure_integration_enabled, + parse_version, + capture_internal_exceptions, +) + +try: + import asyncpg # type: ignore[import-not-found] + from asyncpg.cursor import BaseCursor # type: ignore + +except ImportError: + raise DidNotEnable("asyncpg not installed.") + + +class AsyncPGIntegration(Integration): + identifier = "asyncpg" + origin = f"auto.db.{identifier}" + _record_params = False + + def __init__(self, *, record_params: bool = False): + AsyncPGIntegration._record_params = record_params + + @staticmethod + def setup_once() -> None: + # asyncpg.__version__ is a string containing the semantic version in the form of ".." + asyncpg_version = parse_version(asyncpg.__version__) + _check_minimum_version(AsyncPGIntegration, asyncpg_version) + + asyncpg.Connection.execute = _wrap_execute( + asyncpg.Connection.execute, + ) + + asyncpg.Connection._execute = _wrap_connection_method( + asyncpg.Connection._execute + ) + asyncpg.Connection._executemany = _wrap_connection_method( + asyncpg.Connection._executemany, executemany=True + ) + asyncpg.Connection.cursor = _wrap_cursor_creation(asyncpg.Connection.cursor) + asyncpg.Connection.prepare = _wrap_connection_method(asyncpg.Connection.prepare) + asyncpg.connect_utils._connect_addr = _wrap_connect_addr( + asyncpg.connect_utils._connect_addr + ) + + +T = TypeVar("T") + + +def _wrap_execute(f: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]: + async def _inner(*args: Any, **kwargs: Any) -> T: + if sentry_sdk.get_client().get_integration(AsyncPGIntegration) is None: + return await f(*args, **kwargs) + + # Avoid recording calls to _execute twice. + # Calls to Connection.execute with args also call + # Connection._execute, which is recorded separately + # args[0] = the connection object, args[1] is the query + if len(args) > 2: + return await f(*args, **kwargs) + + query = args[1] + with record_sql_queries( + cursor=None, + query=query, + params_list=None, + paramstyle=None, + executemany=False, + span_origin=AsyncPGIntegration.origin, + ) as span: + res = await f(*args, **kwargs) + + with capture_internal_exceptions(): + add_query_source(span) + + return res + + return _inner + + +SubCursor = TypeVar("SubCursor", bound=BaseCursor) + + +@contextlib.contextmanager +def _record( + cursor: SubCursor | None, + query: str, + params_list: tuple[Any, ...] | None, + *, + executemany: bool = False, +) -> Iterator[Span]: + integration = sentry_sdk.get_client().get_integration(AsyncPGIntegration) + if integration is not None and not integration._record_params: + params_list = None + + param_style = "pyformat" if params_list else None + + with record_sql_queries( + cursor=cursor, + query=query, + params_list=params_list, + paramstyle=param_style, + executemany=executemany, + record_cursor_repr=cursor is not None, + span_origin=AsyncPGIntegration.origin, + ) as span: + yield span + + +def _wrap_connection_method( + f: Callable[..., Awaitable[T]], *, executemany: bool = False +) -> Callable[..., Awaitable[T]]: + async def _inner(*args: Any, **kwargs: Any) -> T: + if sentry_sdk.get_client().get_integration(AsyncPGIntegration) is None: + return await f(*args, **kwargs) + query = args[1] + params_list = args[2] if len(args) > 2 else None + with _record(None, query, params_list, executemany=executemany) as span: + _set_db_data(span, args[0]) + res = await f(*args, **kwargs) + + return res + + return _inner + + +def _wrap_cursor_creation(f: Callable[..., T]) -> Callable[..., T]: + @ensure_integration_enabled(AsyncPGIntegration, f) + def _inner(*args: Any, **kwargs: Any) -> T: # noqa: N807 + query = args[1] + params_list = args[2] if len(args) > 2 else None + + with _record( + None, + query, + params_list, + executemany=False, + ) as span: + _set_db_data(span, args[0]) + res = f(*args, **kwargs) + span.set_data("db.cursor", res) + + return res + + return _inner + + +def _wrap_connect_addr(f: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]: + async def _inner(*args: Any, **kwargs: Any) -> T: + if sentry_sdk.get_client().get_integration(AsyncPGIntegration) is None: + return await f(*args, **kwargs) + + user = kwargs["params"].user + database = kwargs["params"].database + + with sentry_sdk.start_span( + op=OP.DB, + name="connect", + origin=AsyncPGIntegration.origin, + ) as span: + span.set_data(SPANDATA.DB_SYSTEM, "postgresql") + addr = kwargs.get("addr") + if addr: + try: + span.set_data(SPANDATA.SERVER_ADDRESS, addr[0]) + span.set_data(SPANDATA.SERVER_PORT, addr[1]) + except IndexError: + pass + span.set_data(SPANDATA.DB_NAME, database) + span.set_data(SPANDATA.DB_USER, user) + + with capture_internal_exceptions(): + sentry_sdk.add_breadcrumb( + message="connect", category="query", data=span._data + ) + res = await f(*args, **kwargs) + + return res + + return _inner + + +def _set_db_data(span: Span, conn: Any) -> None: + span.set_data(SPANDATA.DB_SYSTEM, "postgresql") + + addr = conn._addr + if addr: + try: + span.set_data(SPANDATA.SERVER_ADDRESS, addr[0]) + span.set_data(SPANDATA.SERVER_PORT, addr[1]) + except IndexError: + pass + + database = conn._params.database + if database: + span.set_data(SPANDATA.DB_NAME, database) + + user = conn._params.user + if user: + span.set_data(SPANDATA.DB_USER, user) diff --git a/aws/lambda_demo/sentry_sdk/integrations/atexit.py b/aws/lambda_demo/sentry_sdk/integrations/atexit.py new file mode 100644 index 000000000..dfc6d08e1 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/atexit.py @@ -0,0 +1,57 @@ +import os +import sys +import atexit + +import sentry_sdk +from sentry_sdk.utils import logger +from sentry_sdk.integrations import Integration +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Optional + + +def default_callback(pending, timeout): + # type: (int, int) -> None + """This is the default shutdown callback that is set on the options. + It prints out a message to stderr that informs the user that some events + are still pending and the process is waiting for them to flush out. + """ + + def echo(msg): + # type: (str) -> None + sys.stderr.write(msg + "\n") + + echo("Sentry is attempting to send %i pending events" % pending) + echo("Waiting up to %s seconds" % timeout) + echo("Press Ctrl-%s to quit" % (os.name == "nt" and "Break" or "C")) + sys.stderr.flush() + + +class AtexitIntegration(Integration): + identifier = "atexit" + + def __init__(self, callback=None): + # type: (Optional[Any]) -> None + if callback is None: + callback = default_callback + self.callback = callback + + @staticmethod + def setup_once(): + # type: () -> None + @atexit.register + def _shutdown(): + # type: () -> None + client = sentry_sdk.get_client() + integration = client.get_integration(AtexitIntegration) + + if integration is None: + return + + logger.debug("atexit: got shutdown signal") + logger.debug("atexit: shutting down client") + sentry_sdk.get_isolation_scope().end_session() + + client.close(callback=integration.callback) diff --git a/aws/lambda_demo/sentry_sdk/integrations/aws_lambda.py b/aws/lambda_demo/sentry_sdk/integrations/aws_lambda.py new file mode 100644 index 000000000..831cde899 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/aws_lambda.py @@ -0,0 +1,496 @@ +import functools +import json +import re +import sys +from copy import deepcopy +from datetime import datetime, timedelta, timezone +from os import environ + +import sentry_sdk +from sentry_sdk.api import continue_trace +from sentry_sdk.consts import OP +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT +from sentry_sdk.utils import ( + AnnotatedValue, + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + logger, + TimeoutThread, + reraise, +) +from sentry_sdk.integrations import Integration +from sentry_sdk.integrations._wsgi_common import _filter_headers + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import TypeVar + from typing import Callable + from typing import Optional + + from sentry_sdk._types import EventProcessor, Event, Hint + + F = TypeVar("F", bound=Callable[..., Any]) + +# Constants +TIMEOUT_WARNING_BUFFER = 1500 # Buffer time required to send timeout warning to Sentry +MILLIS_TO_SECONDS = 1000.0 + + +def _wrap_init_error(init_error): + # type: (F) -> F + @ensure_integration_enabled(AwsLambdaIntegration, init_error) + def sentry_init_error(*args, **kwargs): + # type: (*Any, **Any) -> Any + client = sentry_sdk.get_client() + + with capture_internal_exceptions(): + sentry_sdk.get_isolation_scope().clear_breadcrumbs() + + exc_info = sys.exc_info() + if exc_info and all(exc_info): + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "aws_lambda", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) + + else: + # Fall back to AWS lambdas JSON representation of the error + sentry_event = _event_from_error_json(json.loads(args[1])) + sentry_sdk.capture_event(sentry_event) + + return init_error(*args, **kwargs) + + return sentry_init_error # type: ignore + + +def _wrap_handler(handler): + # type: (F) -> F + @functools.wraps(handler) + def sentry_handler(aws_event, aws_context, *args, **kwargs): + # type: (Any, Any, *Any, **Any) -> Any + + # Per https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html, + # `event` here is *likely* a dictionary, but also might be a number of + # other types (str, int, float, None). + # + # In some cases, it is a list (if the user is batch-invoking their + # function, for example), in which case we'll use the first entry as a + # representative from which to try pulling request data. (Presumably it + # will be the same for all events in the list, since they're all hitting + # the lambda in the same request.) + + client = sentry_sdk.get_client() + integration = client.get_integration(AwsLambdaIntegration) + + if integration is None: + return handler(aws_event, aws_context, *args, **kwargs) + + if isinstance(aws_event, list) and len(aws_event) >= 1: + request_data = aws_event[0] + batch_size = len(aws_event) + else: + request_data = aws_event + batch_size = 1 + + if not isinstance(request_data, dict): + # If we're not dealing with a dictionary, we won't be able to get + # headers, path, http method, etc in any case, so it's fine that + # this is empty + request_data = {} + + configured_time = aws_context.get_remaining_time_in_millis() + + with sentry_sdk.isolation_scope() as scope: + timeout_thread = None + with capture_internal_exceptions(): + scope.clear_breadcrumbs() + scope.add_event_processor( + _make_request_event_processor( + request_data, aws_context, configured_time + ) + ) + scope.set_tag( + "aws_region", aws_context.invoked_function_arn.split(":")[3] + ) + if batch_size > 1: + scope.set_tag("batch_request", True) + scope.set_tag("batch_size", batch_size) + + # Starting the Timeout thread only if the configured time is greater than Timeout warning + # buffer and timeout_warning parameter is set True. + if ( + integration.timeout_warning + and configured_time > TIMEOUT_WARNING_BUFFER + ): + waiting_time = ( + configured_time - TIMEOUT_WARNING_BUFFER + ) / MILLIS_TO_SECONDS + + timeout_thread = TimeoutThread( + waiting_time, + configured_time / MILLIS_TO_SECONDS, + ) + + # Starting the thread to raise timeout warning exception + timeout_thread.start() + + headers = request_data.get("headers", {}) + # Some AWS Services (ie. EventBridge) set headers as a list + # or None, so we must ensure it is a dict + if not isinstance(headers, dict): + headers = {} + + transaction = continue_trace( + headers, + op=OP.FUNCTION_AWS, + name=aws_context.function_name, + source=TRANSACTION_SOURCE_COMPONENT, + origin=AwsLambdaIntegration.origin, + ) + with sentry_sdk.start_transaction( + transaction, + custom_sampling_context={ + "aws_event": aws_event, + "aws_context": aws_context, + }, + ): + try: + return handler(aws_event, aws_context, *args, **kwargs) + except Exception: + exc_info = sys.exc_info() + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "aws_lambda", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) + reraise(*exc_info) + finally: + if timeout_thread: + timeout_thread.stop() + + return sentry_handler # type: ignore + + +def _drain_queue(): + # type: () -> None + with capture_internal_exceptions(): + client = sentry_sdk.get_client() + integration = client.get_integration(AwsLambdaIntegration) + if integration is not None: + # Flush out the event queue before AWS kills the + # process. + client.flush() + + +class AwsLambdaIntegration(Integration): + identifier = "aws_lambda" + origin = f"auto.function.{identifier}" + + def __init__(self, timeout_warning=False): + # type: (bool) -> None + self.timeout_warning = timeout_warning + + @staticmethod + def setup_once(): + # type: () -> None + + lambda_bootstrap = get_lambda_bootstrap() + if not lambda_bootstrap: + logger.warning( + "Not running in AWS Lambda environment, " + "AwsLambdaIntegration disabled (could not find bootstrap module)" + ) + return + + if not hasattr(lambda_bootstrap, "handle_event_request"): + logger.warning( + "Not running in AWS Lambda environment, " + "AwsLambdaIntegration disabled (could not find handle_event_request)" + ) + return + + pre_37 = hasattr(lambda_bootstrap, "handle_http_request") # Python 3.6 + + if pre_37: + old_handle_event_request = lambda_bootstrap.handle_event_request + + def sentry_handle_event_request(request_handler, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + request_handler = _wrap_handler(request_handler) + return old_handle_event_request(request_handler, *args, **kwargs) + + lambda_bootstrap.handle_event_request = sentry_handle_event_request + + old_handle_http_request = lambda_bootstrap.handle_http_request + + def sentry_handle_http_request(request_handler, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + request_handler = _wrap_handler(request_handler) + return old_handle_http_request(request_handler, *args, **kwargs) + + lambda_bootstrap.handle_http_request = sentry_handle_http_request + + # Patch to_json to drain the queue. This should work even when the + # SDK is initialized inside of the handler + + old_to_json = lambda_bootstrap.to_json + + def sentry_to_json(*args, **kwargs): + # type: (*Any, **Any) -> Any + _drain_queue() + return old_to_json(*args, **kwargs) + + lambda_bootstrap.to_json = sentry_to_json + else: + lambda_bootstrap.LambdaRuntimeClient.post_init_error = _wrap_init_error( + lambda_bootstrap.LambdaRuntimeClient.post_init_error + ) + + old_handle_event_request = lambda_bootstrap.handle_event_request + + def sentry_handle_event_request( # type: ignore + lambda_runtime_client, request_handler, *args, **kwargs + ): + request_handler = _wrap_handler(request_handler) + return old_handle_event_request( + lambda_runtime_client, request_handler, *args, **kwargs + ) + + lambda_bootstrap.handle_event_request = sentry_handle_event_request + + # Patch the runtime client to drain the queue. This should work + # even when the SDK is initialized inside of the handler + + def _wrap_post_function(f): + # type: (F) -> F + def inner(*args, **kwargs): + # type: (*Any, **Any) -> Any + _drain_queue() + return f(*args, **kwargs) + + return inner # type: ignore + + lambda_bootstrap.LambdaRuntimeClient.post_invocation_result = ( + _wrap_post_function( + lambda_bootstrap.LambdaRuntimeClient.post_invocation_result + ) + ) + lambda_bootstrap.LambdaRuntimeClient.post_invocation_error = ( + _wrap_post_function( + lambda_bootstrap.LambdaRuntimeClient.post_invocation_error + ) + ) + + +def get_lambda_bootstrap(): + # type: () -> Optional[Any] + + # Python 3.7: If the bootstrap module is *already imported*, it is the + # one we actually want to use (no idea what's in __main__) + # + # Python 3.8: bootstrap is also importable, but will be the same file + # as __main__ imported under a different name: + # + # sys.modules['__main__'].__file__ == sys.modules['bootstrap'].__file__ + # sys.modules['__main__'] is not sys.modules['bootstrap'] + # + # Python 3.9: bootstrap is in __main__.awslambdaricmain + # + # On container builds using the `aws-lambda-python-runtime-interface-client` + # (awslamdaric) module, bootstrap is located in sys.modules['__main__'].bootstrap + # + # Such a setup would then make all monkeypatches useless. + if "bootstrap" in sys.modules: + return sys.modules["bootstrap"] + elif "__main__" in sys.modules: + module = sys.modules["__main__"] + # python3.9 runtime + if hasattr(module, "awslambdaricmain") and hasattr( + module.awslambdaricmain, "bootstrap" + ): + return module.awslambdaricmain.bootstrap + elif hasattr(module, "bootstrap"): + # awslambdaric python module in container builds + return module.bootstrap + + # python3.8 runtime + return module + else: + return None + + +def _make_request_event_processor(aws_event, aws_context, configured_timeout): + # type: (Any, Any, Any) -> EventProcessor + start_time = datetime.now(timezone.utc) + + def event_processor(sentry_event, hint, start_time=start_time): + # type: (Event, Hint, datetime) -> Optional[Event] + remaining_time_in_milis = aws_context.get_remaining_time_in_millis() + exec_duration = configured_timeout - remaining_time_in_milis + + extra = sentry_event.setdefault("extra", {}) + extra["lambda"] = { + "function_name": aws_context.function_name, + "function_version": aws_context.function_version, + "invoked_function_arn": aws_context.invoked_function_arn, + "aws_request_id": aws_context.aws_request_id, + "execution_duration_in_millis": exec_duration, + "remaining_time_in_millis": remaining_time_in_milis, + } + + extra["cloudwatch logs"] = { + "url": _get_cloudwatch_logs_url(aws_context, start_time), + "log_group": aws_context.log_group_name, + "log_stream": aws_context.log_stream_name, + } + + request = sentry_event.get("request", {}) + + if "httpMethod" in aws_event: + request["method"] = aws_event["httpMethod"] + + request["url"] = _get_url(aws_event, aws_context) + + if "queryStringParameters" in aws_event: + request["query_string"] = aws_event["queryStringParameters"] + + if "headers" in aws_event: + request["headers"] = _filter_headers(aws_event["headers"]) + + if should_send_default_pii(): + user_info = sentry_event.setdefault("user", {}) + + identity = aws_event.get("identity") + if identity is None: + identity = {} + + id = identity.get("userArn") + if id is not None: + user_info.setdefault("id", id) + + ip = identity.get("sourceIp") + if ip is not None: + user_info.setdefault("ip_address", ip) + + if "body" in aws_event: + request["data"] = aws_event.get("body", "") + else: + if aws_event.get("body", None): + # Unfortunately couldn't find a way to get structured body from AWS + # event. Meaning every body is unstructured to us. + request["data"] = AnnotatedValue.removed_because_raw_data() + + sentry_event["request"] = deepcopy(request) + + return sentry_event + + return event_processor + + +def _get_url(aws_event, aws_context): + # type: (Any, Any) -> str + path = aws_event.get("path", None) + + headers = aws_event.get("headers") + if headers is None: + headers = {} + + host = headers.get("Host", None) + proto = headers.get("X-Forwarded-Proto", None) + if proto and host and path: + return "{}://{}{}".format(proto, host, path) + return "awslambda:///{}".format(aws_context.function_name) + + +def _get_cloudwatch_logs_url(aws_context, start_time): + # type: (Any, datetime) -> str + """ + Generates a CloudWatchLogs console URL based on the context object + + Arguments: + aws_context {Any} -- context from lambda handler + + Returns: + str -- AWS Console URL to logs. + """ + formatstring = "%Y-%m-%dT%H:%M:%SZ" + region = environ.get("AWS_REGION", "") + + url = ( + "https://console.{domain}/cloudwatch/home?region={region}" + "#logEventViewer:group={log_group};stream={log_stream}" + ";start={start_time};end={end_time}" + ).format( + domain="amazonaws.cn" if region.startswith("cn-") else "aws.amazon.com", + region=region, + log_group=aws_context.log_group_name, + log_stream=aws_context.log_stream_name, + start_time=(start_time - timedelta(seconds=1)).strftime(formatstring), + end_time=(datetime.now(timezone.utc) + timedelta(seconds=2)).strftime( + formatstring + ), + ) + + return url + + +def _parse_formatted_traceback(formatted_tb): + # type: (list[str]) -> list[dict[str, Any]] + frames = [] + for frame in formatted_tb: + match = re.match(r'File "(.+)", line (\d+), in (.+)', frame.strip()) + if match: + file_name, line_number, func_name = match.groups() + line_number = int(line_number) + frames.append( + { + "filename": file_name, + "function": func_name, + "lineno": line_number, + "vars": None, + "pre_context": None, + "context_line": None, + "post_context": None, + } + ) + return frames + + +def _event_from_error_json(error_json): + # type: (dict[str, Any]) -> Event + """ + Converts the error JSON from AWS Lambda into a Sentry error event. + This is not a full fletched event, but better than nothing. + + This is an example of where AWS creates the error JSON: + https://github.com/aws/aws-lambda-python-runtime-interface-client/blob/2.2.1/awslambdaric/bootstrap.py#L479 + """ + event = { + "level": "error", + "exception": { + "values": [ + { + "type": error_json.get("errorType"), + "value": error_json.get("errorMessage"), + "stacktrace": { + "frames": _parse_formatted_traceback( + error_json.get("stackTrace", []) + ), + }, + "mechanism": { + "type": "aws_lambda", + "handled": False, + }, + } + ], + }, + } # type: Event + + return event diff --git a/aws/lambda_demo/sentry_sdk/integrations/beam.py b/aws/lambda_demo/sentry_sdk/integrations/beam.py new file mode 100644 index 000000000..a2e4553f5 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/beam.py @@ -0,0 +1,176 @@ +import sys +import types +from functools import wraps + +import sentry_sdk +from sentry_sdk.integrations import Integration +from sentry_sdk.integrations.logging import ignore_logger +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + reraise, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Iterator + from typing import TypeVar + from typing import Callable + + from sentry_sdk._types import ExcInfo + + T = TypeVar("T") + F = TypeVar("F", bound=Callable[..., Any]) + + +WRAPPED_FUNC = "_wrapped_{}_" +INSPECT_FUNC = "_inspect_{}" # Required format per apache_beam/transforms/core.py +USED_FUNC = "_sentry_used_" + + +class BeamIntegration(Integration): + identifier = "beam" + + @staticmethod + def setup_once(): + # type: () -> None + from apache_beam.transforms.core import DoFn, ParDo # type: ignore + + ignore_logger("root") + ignore_logger("bundle_processor.create") + + function_patches = ["process", "start_bundle", "finish_bundle", "setup"] + for func_name in function_patches: + setattr( + DoFn, + INSPECT_FUNC.format(func_name), + _wrap_inspect_call(DoFn, func_name), + ) + + old_init = ParDo.__init__ + + def sentry_init_pardo(self, fn, *args, **kwargs): + # type: (ParDo, Any, *Any, **Any) -> Any + # Do not monkey patch init twice + if not getattr(self, "_sentry_is_patched", False): + for func_name in function_patches: + if not hasattr(fn, func_name): + continue + wrapped_func = WRAPPED_FUNC.format(func_name) + + # Check to see if inspect is set and process is not + # to avoid monkey patching process twice. + # Check to see if function is part of object for + # backwards compatibility. + process_func = getattr(fn, func_name) + inspect_func = getattr(fn, INSPECT_FUNC.format(func_name)) + if not getattr(inspect_func, USED_FUNC, False) and not getattr( + process_func, USED_FUNC, False + ): + setattr(fn, wrapped_func, process_func) + setattr(fn, func_name, _wrap_task_call(process_func)) + + self._sentry_is_patched = True + old_init(self, fn, *args, **kwargs) + + ParDo.__init__ = sentry_init_pardo + + +def _wrap_inspect_call(cls, func_name): + # type: (Any, Any) -> Any + + if not hasattr(cls, func_name): + return None + + def _inspect(self): + # type: (Any) -> Any + """ + Inspect function overrides the way Beam gets argspec. + """ + wrapped_func = WRAPPED_FUNC.format(func_name) + if hasattr(self, wrapped_func): + process_func = getattr(self, wrapped_func) + else: + process_func = getattr(self, func_name) + setattr(self, func_name, _wrap_task_call(process_func)) + setattr(self, wrapped_func, process_func) + + # getfullargspec is deprecated in more recent beam versions and get_function_args_defaults + # (which uses Signatures internally) should be used instead. + try: + from apache_beam.transforms.core import get_function_args_defaults + + return get_function_args_defaults(process_func) + except ImportError: + from apache_beam.typehints.decorators import getfullargspec # type: ignore + + return getfullargspec(process_func) + + setattr(_inspect, USED_FUNC, True) + return _inspect + + +def _wrap_task_call(func): + # type: (F) -> F + """ + Wrap task call with a try catch to get exceptions. + """ + + @wraps(func) + def _inner(*args, **kwargs): + # type: (*Any, **Any) -> Any + try: + gen = func(*args, **kwargs) + except Exception: + raise_exception() + + if not isinstance(gen, types.GeneratorType): + return gen + return _wrap_generator_call(gen) + + setattr(_inner, USED_FUNC, True) + return _inner # type: ignore + + +@ensure_integration_enabled(BeamIntegration) +def _capture_exception(exc_info): + # type: (ExcInfo) -> None + """ + Send Beam exception to Sentry. + """ + client = sentry_sdk.get_client() + + event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "beam", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + +def raise_exception(): + # type: () -> None + """ + Raise an exception. + """ + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _capture_exception(exc_info) + reraise(*exc_info) + + +def _wrap_generator_call(gen): + # type: (Iterator[T]) -> Iterator[T] + """ + Wrap the generator to handle any failures. + """ + while True: + try: + yield next(gen) + except StopIteration: + break + except Exception: + raise_exception() diff --git a/aws/lambda_demo/sentry_sdk/integrations/boto3.py b/aws/lambda_demo/sentry_sdk/integrations/boto3.py new file mode 100644 index 000000000..0207341f1 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/boto3.py @@ -0,0 +1,137 @@ +from functools import partial + +import sentry_sdk +from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable +from sentry_sdk.tracing import Span +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + parse_url, + parse_version, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Dict + from typing import Optional + from typing import Type + +try: + from botocore import __version__ as BOTOCORE_VERSION # type: ignore + from botocore.client import BaseClient # type: ignore + from botocore.response import StreamingBody # type: ignore + from botocore.awsrequest import AWSRequest # type: ignore +except ImportError: + raise DidNotEnable("botocore is not installed") + + +class Boto3Integration(Integration): + identifier = "boto3" + origin = f"auto.http.{identifier}" + + @staticmethod + def setup_once(): + # type: () -> None + version = parse_version(BOTOCORE_VERSION) + _check_minimum_version(Boto3Integration, version, "botocore") + + orig_init = BaseClient.__init__ + + def sentry_patched_init(self, *args, **kwargs): + # type: (Type[BaseClient], *Any, **Any) -> None + orig_init(self, *args, **kwargs) + meta = self.meta + service_id = meta.service_model.service_id.hyphenize() + meta.events.register( + "request-created", + partial(_sentry_request_created, service_id=service_id), + ) + meta.events.register("after-call", _sentry_after_call) + meta.events.register("after-call-error", _sentry_after_call_error) + + BaseClient.__init__ = sentry_patched_init + + +@ensure_integration_enabled(Boto3Integration) +def _sentry_request_created(service_id, request, operation_name, **kwargs): + # type: (str, AWSRequest, str, **Any) -> None + description = "aws.%s.%s" % (service_id, operation_name) + span = sentry_sdk.start_span( + op=OP.HTTP_CLIENT, + name=description, + origin=Boto3Integration.origin, + ) + + with capture_internal_exceptions(): + parsed_url = parse_url(request.url, sanitize=False) + span.set_data("aws.request.url", parsed_url.url) + span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query) + span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment) + + span.set_tag("aws.service_id", service_id) + span.set_tag("aws.operation_name", operation_name) + span.set_data(SPANDATA.HTTP_METHOD, request.method) + + # We do it in order for subsequent http calls/retries be + # attached to this span. + span.__enter__() + + # request.context is an open-ended data-structure + # where we can add anything useful in request life cycle. + request.context["_sentrysdk_span"] = span + + +def _sentry_after_call(context, parsed, **kwargs): + # type: (Dict[str, Any], Dict[str, Any], **Any) -> None + span = context.pop("_sentrysdk_span", None) # type: Optional[Span] + + # Span could be absent if the integration is disabled. + if span is None: + return + span.__exit__(None, None, None) + + body = parsed.get("Body") + if not isinstance(body, StreamingBody): + return + + streaming_span = span.start_child( + op=OP.HTTP_CLIENT_STREAM, + name=span.description, + origin=Boto3Integration.origin, + ) + + orig_read = body.read + orig_close = body.close + + def sentry_streaming_body_read(*args, **kwargs): + # type: (*Any, **Any) -> bytes + try: + ret = orig_read(*args, **kwargs) + if not ret: + streaming_span.finish() + return ret + except Exception: + streaming_span.finish() + raise + + body.read = sentry_streaming_body_read + + def sentry_streaming_body_close(*args, **kwargs): + # type: (*Any, **Any) -> None + streaming_span.finish() + orig_close(*args, **kwargs) + + body.close = sentry_streaming_body_close + + +def _sentry_after_call_error(context, exception, **kwargs): + # type: (Dict[str, Any], Type[BaseException], **Any) -> None + span = context.pop("_sentrysdk_span", None) # type: Optional[Span] + + # Span could be absent if the integration is disabled. + if span is None: + return + span.__exit__(type(exception), exception, None) diff --git a/aws/lambda_demo/sentry_sdk/integrations/bottle.py b/aws/lambda_demo/sentry_sdk/integrations/bottle.py new file mode 100644 index 000000000..148b86852 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/bottle.py @@ -0,0 +1,215 @@ +import functools + +import sentry_sdk +from sentry_sdk.tracing import SOURCE_FOR_STYLE +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + parse_version, + transaction_from_function, +) +from sentry_sdk.integrations import ( + Integration, + DidNotEnable, + _DEFAULT_FAILED_REQUEST_STATUS_CODES, + _check_minimum_version, +) +from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware +from sentry_sdk.integrations._wsgi_common import RequestExtractor + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Set + + from sentry_sdk.integrations.wsgi import _ScopedResponse + from typing import Any + from typing import Dict + from typing import Callable + from typing import Optional + from bottle import FileUpload, FormsDict, LocalRequest # type: ignore + + from sentry_sdk._types import EventProcessor, Event + +try: + from bottle import ( + Bottle, + HTTPResponse, + Route, + request as bottle_request, + __version__ as BOTTLE_VERSION, + ) +except ImportError: + raise DidNotEnable("Bottle not installed") + + +TRANSACTION_STYLE_VALUES = ("endpoint", "url") + + +class BottleIntegration(Integration): + identifier = "bottle" + origin = f"auto.http.{identifier}" + + transaction_style = "" + + def __init__( + self, + transaction_style="endpoint", # type: str + *, + failed_request_status_codes=_DEFAULT_FAILED_REQUEST_STATUS_CODES, # type: Set[int] + ): + # type: (...) -> None + + if transaction_style not in TRANSACTION_STYLE_VALUES: + raise ValueError( + "Invalid value for transaction_style: %s (must be in %s)" + % (transaction_style, TRANSACTION_STYLE_VALUES) + ) + self.transaction_style = transaction_style + self.failed_request_status_codes = failed_request_status_codes + + @staticmethod + def setup_once(): + # type: () -> None + version = parse_version(BOTTLE_VERSION) + _check_minimum_version(BottleIntegration, version) + + old_app = Bottle.__call__ + + @ensure_integration_enabled(BottleIntegration, old_app) + def sentry_patched_wsgi_app(self, environ, start_response): + # type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse + middleware = SentryWsgiMiddleware( + lambda *a, **kw: old_app(self, *a, **kw), + span_origin=BottleIntegration.origin, + ) + + return middleware(environ, start_response) + + Bottle.__call__ = sentry_patched_wsgi_app + + old_handle = Bottle._handle + + @functools.wraps(old_handle) + def _patched_handle(self, environ): + # type: (Bottle, Dict[str, Any]) -> Any + integration = sentry_sdk.get_client().get_integration(BottleIntegration) + if integration is None: + return old_handle(self, environ) + + scope = sentry_sdk.get_isolation_scope() + scope._name = "bottle" + scope.add_event_processor( + _make_request_event_processor(self, bottle_request, integration) + ) + res = old_handle(self, environ) + + return res + + Bottle._handle = _patched_handle + + old_make_callback = Route._make_callback + + @functools.wraps(old_make_callback) + def patched_make_callback(self, *args, **kwargs): + # type: (Route, *object, **object) -> Any + prepared_callback = old_make_callback(self, *args, **kwargs) + + integration = sentry_sdk.get_client().get_integration(BottleIntegration) + if integration is None: + return prepared_callback + + def wrapped_callback(*args, **kwargs): + # type: (*object, **object) -> Any + try: + res = prepared_callback(*args, **kwargs) + except Exception as exception: + _capture_exception(exception, handled=False) + raise exception + + if ( + isinstance(res, HTTPResponse) + and res.status_code in integration.failed_request_status_codes + ): + _capture_exception(res, handled=True) + + return res + + return wrapped_callback + + Route._make_callback = patched_make_callback + + +class BottleRequestExtractor(RequestExtractor): + def env(self): + # type: () -> Dict[str, str] + return self.request.environ + + def cookies(self): + # type: () -> Dict[str, str] + return self.request.cookies + + def raw_data(self): + # type: () -> bytes + return self.request.body.read() + + def form(self): + # type: () -> FormsDict + if self.is_json(): + return None + return self.request.forms.decode() + + def files(self): + # type: () -> Optional[Dict[str, str]] + if self.is_json(): + return None + + return self.request.files + + def size_of_file(self, file): + # type: (FileUpload) -> int + return file.content_length + + +def _set_transaction_name_and_source(event, transaction_style, request): + # type: (Event, str, Any) -> None + name = "" + + if transaction_style == "url": + name = request.route.rule or "" + + elif transaction_style == "endpoint": + name = ( + request.route.name + or transaction_from_function(request.route.callback) + or "" + ) + + event["transaction"] = name + event["transaction_info"] = {"source": SOURCE_FOR_STYLE[transaction_style]} + + +def _make_request_event_processor(app, request, integration): + # type: (Bottle, LocalRequest, BottleIntegration) -> EventProcessor + + def event_processor(event, hint): + # type: (Event, dict[str, Any]) -> Event + _set_transaction_name_and_source(event, integration.transaction_style, request) + + with capture_internal_exceptions(): + BottleRequestExtractor(request).extract_into_event(event) + + return event + + return event_processor + + +def _capture_exception(exception, handled): + # type: (BaseException, bool) -> None + event, hint = event_from_exception( + exception, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "bottle", "handled": handled}, + ) + sentry_sdk.capture_event(event, hint=hint) diff --git a/aws/lambda_demo/sentry_sdk/integrations/celery/__init__.py b/aws/lambda_demo/sentry_sdk/integrations/celery/__init__.py new file mode 100644 index 000000000..dc48aac0e --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/celery/__init__.py @@ -0,0 +1,528 @@ +import sys +from collections.abc import Mapping +from functools import wraps + +import sentry_sdk +from sentry_sdk import isolation_scope +from sentry_sdk.api import continue_trace +from sentry_sdk.consts import OP, SPANSTATUS, SPANDATA +from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable +from sentry_sdk.integrations.celery.beat import ( + _patch_beat_apply_entry, + _patch_redbeat_maybe_due, + _setup_celery_beat_signals, +) +from sentry_sdk.integrations.celery.utils import _now_seconds_since_epoch +from sentry_sdk.integrations.logging import ignore_logger +from sentry_sdk.tracing import BAGGAGE_HEADER_NAME, TRANSACTION_SOURCE_TASK +from sentry_sdk.tracing_utils import Baggage +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + reraise, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import List + from typing import Optional + from typing import TypeVar + from typing import Union + + from sentry_sdk._types import EventProcessor, Event, Hint, ExcInfo + from sentry_sdk.tracing import Span + + F = TypeVar("F", bound=Callable[..., Any]) + + +try: + from celery import VERSION as CELERY_VERSION # type: ignore + from celery.app.task import Task # type: ignore + from celery.app.trace import task_has_custom + from celery.exceptions import ( # type: ignore + Ignore, + Reject, + Retry, + SoftTimeLimitExceeded, + ) + from kombu import Producer # type: ignore +except ImportError: + raise DidNotEnable("Celery not installed") + + +CELERY_CONTROL_FLOW_EXCEPTIONS = (Retry, Ignore, Reject) + + +class CeleryIntegration(Integration): + identifier = "celery" + origin = f"auto.queue.{identifier}" + + def __init__( + self, + propagate_traces=True, + monitor_beat_tasks=False, + exclude_beat_tasks=None, + ): + # type: (bool, bool, Optional[List[str]]) -> None + self.propagate_traces = propagate_traces + self.monitor_beat_tasks = monitor_beat_tasks + self.exclude_beat_tasks = exclude_beat_tasks + + _patch_beat_apply_entry() + _patch_redbeat_maybe_due() + _setup_celery_beat_signals(monitor_beat_tasks) + + @staticmethod + def setup_once(): + # type: () -> None + _check_minimum_version(CeleryIntegration, CELERY_VERSION) + + _patch_build_tracer() + _patch_task_apply_async() + _patch_celery_send_task() + _patch_worker_exit() + _patch_producer_publish() + + # This logger logs every status of every task that ran on the worker. + # Meaning that every task's breadcrumbs are full of stuff like "Task + # raised unexpected ". + ignore_logger("celery.worker.job") + ignore_logger("celery.app.trace") + + # This is stdout/err redirected to a logger, can't deal with this + # (need event_level=logging.WARN to reproduce) + ignore_logger("celery.redirected") + + +def _set_status(status): + # type: (str) -> None + with capture_internal_exceptions(): + scope = sentry_sdk.get_current_scope() + if scope.span is not None: + scope.span.set_status(status) + + +def _capture_exception(task, exc_info): + # type: (Any, ExcInfo) -> None + client = sentry_sdk.get_client() + if client.get_integration(CeleryIntegration) is None: + return + + if isinstance(exc_info[1], CELERY_CONTROL_FLOW_EXCEPTIONS): + # ??? Doesn't map to anything + _set_status("aborted") + return + + _set_status("internal_error") + + if hasattr(task, "throws") and isinstance(exc_info[1], task.throws): + return + + event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "celery", "handled": False}, + ) + + sentry_sdk.capture_event(event, hint=hint) + + +def _make_event_processor(task, uuid, args, kwargs, request=None): + # type: (Any, Any, Any, Any, Optional[Any]) -> EventProcessor + def event_processor(event, hint): + # type: (Event, Hint) -> Optional[Event] + + with capture_internal_exceptions(): + tags = event.setdefault("tags", {}) + tags["celery_task_id"] = uuid + extra = event.setdefault("extra", {}) + extra["celery-job"] = { + "task_name": task.name, + "args": args, + "kwargs": kwargs, + } + + if "exc_info" in hint: + with capture_internal_exceptions(): + if issubclass(hint["exc_info"][0], SoftTimeLimitExceeded): + event["fingerprint"] = [ + "celery", + "SoftTimeLimitExceeded", + getattr(task, "name", task), + ] + + return event + + return event_processor + + +def _update_celery_task_headers(original_headers, span, monitor_beat_tasks): + # type: (dict[str, Any], Optional[Span], bool) -> dict[str, Any] + """ + Updates the headers of the Celery task with the tracing information + and eventually Sentry Crons monitoring information for beat tasks. + """ + updated_headers = original_headers.copy() + with capture_internal_exceptions(): + # if span is None (when the task was started by Celery Beat) + # this will return the trace headers from the scope. + headers = dict( + sentry_sdk.get_isolation_scope().iter_trace_propagation_headers(span=span) + ) + + if monitor_beat_tasks: + headers.update( + { + "sentry-monitor-start-timestamp-s": "%.9f" + % _now_seconds_since_epoch(), + } + ) + + # Add the time the task was enqueued to the headers + # This is used in the consumer to calculate the latency + updated_headers.update( + {"sentry-task-enqueued-time": _now_seconds_since_epoch()} + ) + + if headers: + existing_baggage = updated_headers.get(BAGGAGE_HEADER_NAME) + sentry_baggage = headers.get(BAGGAGE_HEADER_NAME) + + combined_baggage = sentry_baggage or existing_baggage + if sentry_baggage and existing_baggage: + # Merge incoming and sentry baggage, where the sentry trace information + # in the incoming baggage takes precedence and the third-party items + # are concatenated. + incoming = Baggage.from_incoming_header(existing_baggage) + combined = Baggage.from_incoming_header(sentry_baggage) + combined.sentry_items.update(incoming.sentry_items) + combined.third_party_items = ",".join( + [ + x + for x in [ + combined.third_party_items, + incoming.third_party_items, + ] + if x is not None and x != "" + ] + ) + combined_baggage = combined.serialize(include_third_party=True) + + updated_headers.update(headers) + if combined_baggage: + updated_headers[BAGGAGE_HEADER_NAME] = combined_baggage + + # https://github.com/celery/celery/issues/4875 + # + # Need to setdefault the inner headers too since other + # tracing tools (dd-trace-py) also employ this exact + # workaround and we don't want to break them. + updated_headers.setdefault("headers", {}).update(headers) + if combined_baggage: + updated_headers["headers"][BAGGAGE_HEADER_NAME] = combined_baggage + + # Add the Sentry options potentially added in `sentry_apply_entry` + # to the headers (done when auto-instrumenting Celery Beat tasks) + for key, value in updated_headers.items(): + if key.startswith("sentry-"): + updated_headers["headers"][key] = value + + return updated_headers + + +class NoOpMgr: + def __enter__(self): + # type: () -> None + return None + + def __exit__(self, exc_type, exc_value, traceback): + # type: (Any, Any, Any) -> None + return None + + +def _wrap_task_run(f): + # type: (F) -> F + @wraps(f) + def apply_async(*args, **kwargs): + # type: (*Any, **Any) -> Any + # Note: kwargs can contain headers=None, so no setdefault! + # Unsure which backend though. + integration = sentry_sdk.get_client().get_integration(CeleryIntegration) + if integration is None: + return f(*args, **kwargs) + + kwarg_headers = kwargs.get("headers") or {} + propagate_traces = kwarg_headers.pop( + "sentry-propagate-traces", integration.propagate_traces + ) + + if not propagate_traces: + return f(*args, **kwargs) + + if isinstance(args[0], Task): + task_name = args[0].name # type: str + elif len(args) > 1 and isinstance(args[1], str): + task_name = args[1] + else: + task_name = "" + + task_started_from_beat = sentry_sdk.get_isolation_scope()._name == "celery-beat" + + span_mgr = ( + sentry_sdk.start_span( + op=OP.QUEUE_SUBMIT_CELERY, + name=task_name, + origin=CeleryIntegration.origin, + ) + if not task_started_from_beat + else NoOpMgr() + ) # type: Union[Span, NoOpMgr] + + with span_mgr as span: + kwargs["headers"] = _update_celery_task_headers( + kwarg_headers, span, integration.monitor_beat_tasks + ) + return f(*args, **kwargs) + + return apply_async # type: ignore + + +def _wrap_tracer(task, f): + # type: (Any, F) -> F + + # Need to wrap tracer for pushing the scope before prerun is sent, and + # popping it after postrun is sent. + # + # This is the reason we don't use signals for hooking in the first place. + # Also because in Celery 3, signal dispatch returns early if one handler + # crashes. + @wraps(f) + @ensure_integration_enabled(CeleryIntegration, f) + def _inner(*args, **kwargs): + # type: (*Any, **Any) -> Any + with isolation_scope() as scope: + scope._name = "celery" + scope.clear_breadcrumbs() + scope.add_event_processor(_make_event_processor(task, *args, **kwargs)) + + transaction = None + + # Celery task objects are not a thing to be trusted. Even + # something such as attribute access can fail. + with capture_internal_exceptions(): + headers = args[3].get("headers") or {} + transaction = continue_trace( + headers, + op=OP.QUEUE_TASK_CELERY, + name="unknown celery task", + source=TRANSACTION_SOURCE_TASK, + origin=CeleryIntegration.origin, + ) + transaction.name = task.name + transaction.set_status(SPANSTATUS.OK) + + if transaction is None: + return f(*args, **kwargs) + + with sentry_sdk.start_transaction( + transaction, + custom_sampling_context={ + "celery_job": { + "task": task.name, + # for some reason, args[1] is a list if non-empty but a + # tuple if empty + "args": list(args[1]), + "kwargs": args[2], + } + }, + ): + return f(*args, **kwargs) + + return _inner # type: ignore + + +def _set_messaging_destination_name(task, span): + # type: (Any, Span) -> None + """Set "messaging.destination.name" tag for span""" + with capture_internal_exceptions(): + delivery_info = task.request.delivery_info + if delivery_info: + routing_key = delivery_info.get("routing_key") + if delivery_info.get("exchange") == "" and routing_key is not None: + # Empty exchange indicates the default exchange, meaning the tasks + # are sent to the queue with the same name as the routing key. + span.set_data(SPANDATA.MESSAGING_DESTINATION_NAME, routing_key) + + +def _wrap_task_call(task, f): + # type: (Any, F) -> F + + # Need to wrap task call because the exception is caught before we get to + # see it. Also celery's reported stacktrace is untrustworthy. + + # functools.wraps is important here because celery-once looks at this + # method's name. @ensure_integration_enabled internally calls functools.wraps, + # but if we ever remove the @ensure_integration_enabled decorator, we need + # to add @functools.wraps(f) here. + # https://github.com/getsentry/sentry-python/issues/421 + @ensure_integration_enabled(CeleryIntegration, f) + def _inner(*args, **kwargs): + # type: (*Any, **Any) -> Any + try: + with sentry_sdk.start_span( + op=OP.QUEUE_PROCESS, + name=task.name, + origin=CeleryIntegration.origin, + ) as span: + _set_messaging_destination_name(task, span) + + latency = None + with capture_internal_exceptions(): + if ( + task.request.headers is not None + and "sentry-task-enqueued-time" in task.request.headers + ): + latency = _now_seconds_since_epoch() - task.request.headers.pop( + "sentry-task-enqueued-time" + ) + + if latency is not None: + span.set_data(SPANDATA.MESSAGING_MESSAGE_RECEIVE_LATENCY, latency) + + with capture_internal_exceptions(): + span.set_data(SPANDATA.MESSAGING_MESSAGE_ID, task.request.id) + + with capture_internal_exceptions(): + span.set_data( + SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT, task.request.retries + ) + + with capture_internal_exceptions(): + span.set_data( + SPANDATA.MESSAGING_SYSTEM, + task.app.connection().transport.driver_type, + ) + + return f(*args, **kwargs) + except Exception: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _capture_exception(task, exc_info) + reraise(*exc_info) + + return _inner # type: ignore + + +def _patch_build_tracer(): + # type: () -> None + import celery.app.trace as trace # type: ignore + + original_build_tracer = trace.build_tracer + + def sentry_build_tracer(name, task, *args, **kwargs): + # type: (Any, Any, *Any, **Any) -> Any + if not getattr(task, "_sentry_is_patched", False): + # determine whether Celery will use __call__ or run and patch + # accordingly + if task_has_custom(task, "__call__"): + type(task).__call__ = _wrap_task_call(task, type(task).__call__) + else: + task.run = _wrap_task_call(task, task.run) + + # `build_tracer` is apparently called for every task + # invocation. Can't wrap every celery task for every invocation + # or we will get infinitely nested wrapper functions. + task._sentry_is_patched = True + + return _wrap_tracer(task, original_build_tracer(name, task, *args, **kwargs)) + + trace.build_tracer = sentry_build_tracer + + +def _patch_task_apply_async(): + # type: () -> None + Task.apply_async = _wrap_task_run(Task.apply_async) + + +def _patch_celery_send_task(): + # type: () -> None + from celery import Celery + + Celery.send_task = _wrap_task_run(Celery.send_task) + + +def _patch_worker_exit(): + # type: () -> None + + # Need to flush queue before worker shutdown because a crashing worker will + # call os._exit + from billiard.pool import Worker # type: ignore + + original_workloop = Worker.workloop + + def sentry_workloop(*args, **kwargs): + # type: (*Any, **Any) -> Any + try: + return original_workloop(*args, **kwargs) + finally: + with capture_internal_exceptions(): + if ( + sentry_sdk.get_client().get_integration(CeleryIntegration) + is not None + ): + sentry_sdk.flush() + + Worker.workloop = sentry_workloop + + +def _patch_producer_publish(): + # type: () -> None + original_publish = Producer.publish + + @ensure_integration_enabled(CeleryIntegration, original_publish) + def sentry_publish(self, *args, **kwargs): + # type: (Producer, *Any, **Any) -> Any + kwargs_headers = kwargs.get("headers", {}) + if not isinstance(kwargs_headers, Mapping): + # Ensure kwargs_headers is a Mapping, so we can safely call get(). + # We don't expect this to happen, but it's better to be safe. Even + # if it does happen, only our instrumentation breaks. This line + # does not overwrite kwargs["headers"], so the original publish + # method will still work. + kwargs_headers = {} + + task_name = kwargs_headers.get("task") + task_id = kwargs_headers.get("id") + retries = kwargs_headers.get("retries") + + routing_key = kwargs.get("routing_key") + exchange = kwargs.get("exchange") + + with sentry_sdk.start_span( + op=OP.QUEUE_PUBLISH, + name=task_name, + origin=CeleryIntegration.origin, + ) as span: + if task_id is not None: + span.set_data(SPANDATA.MESSAGING_MESSAGE_ID, task_id) + + if exchange == "" and routing_key is not None: + # Empty exchange indicates the default exchange, meaning messages are + # routed to the queue with the same name as the routing key. + span.set_data(SPANDATA.MESSAGING_DESTINATION_NAME, routing_key) + + if retries is not None: + span.set_data(SPANDATA.MESSAGING_MESSAGE_RETRY_COUNT, retries) + + with capture_internal_exceptions(): + span.set_data( + SPANDATA.MESSAGING_SYSTEM, self.connection.transport.driver_type + ) + + return original_publish(self, *args, **kwargs) + + Producer.publish = sentry_publish diff --git a/aws/lambda_demo/sentry_sdk/integrations/celery/beat.py b/aws/lambda_demo/sentry_sdk/integrations/celery/beat.py new file mode 100644 index 000000000..ddbc8561a --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/celery/beat.py @@ -0,0 +1,293 @@ +import sentry_sdk +from sentry_sdk.crons import capture_checkin, MonitorStatus +from sentry_sdk.integrations import DidNotEnable +from sentry_sdk.integrations.celery.utils import ( + _get_humanized_interval, + _now_seconds_since_epoch, +) +from sentry_sdk.utils import ( + logger, + match_regex_list, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Callable + from typing import Any, Optional, TypeVar, Union + from sentry_sdk._types import ( + MonitorConfig, + MonitorConfigScheduleType, + MonitorConfigScheduleUnit, + ) + + F = TypeVar("F", bound=Callable[..., Any]) + + +try: + from celery import Task, Celery # type: ignore + from celery.beat import Scheduler # type: ignore + from celery.schedules import crontab, schedule # type: ignore + from celery.signals import ( # type: ignore + task_failure, + task_success, + task_retry, + ) +except ImportError: + raise DidNotEnable("Celery not installed") + +try: + from redbeat.schedulers import RedBeatScheduler # type: ignore +except ImportError: + RedBeatScheduler = None + + +def _get_headers(task): + # type: (Task) -> dict[str, Any] + headers = task.request.get("headers") or {} + + # flatten nested headers + if "headers" in headers: + headers.update(headers["headers"]) + del headers["headers"] + + headers.update(task.request.get("properties") or {}) + + return headers + + +def _get_monitor_config(celery_schedule, app, monitor_name): + # type: (Any, Celery, str) -> MonitorConfig + monitor_config = {} # type: MonitorConfig + schedule_type = None # type: Optional[MonitorConfigScheduleType] + schedule_value = None # type: Optional[Union[str, int]] + schedule_unit = None # type: Optional[MonitorConfigScheduleUnit] + + if isinstance(celery_schedule, crontab): + schedule_type = "crontab" + schedule_value = ( + "{0._orig_minute} " + "{0._orig_hour} " + "{0._orig_day_of_month} " + "{0._orig_month_of_year} " + "{0._orig_day_of_week}".format(celery_schedule) + ) + elif isinstance(celery_schedule, schedule): + schedule_type = "interval" + (schedule_value, schedule_unit) = _get_humanized_interval( + celery_schedule.seconds + ) + + if schedule_unit == "second": + logger.warning( + "Intervals shorter than one minute are not supported by Sentry Crons. Monitor '%s' has an interval of %s seconds. Use the `exclude_beat_tasks` option in the celery integration to exclude it.", + monitor_name, + schedule_value, + ) + return {} + + else: + logger.warning( + "Celery schedule type '%s' not supported by Sentry Crons.", + type(celery_schedule), + ) + return {} + + monitor_config["schedule"] = {} + monitor_config["schedule"]["type"] = schedule_type + monitor_config["schedule"]["value"] = schedule_value + + if schedule_unit is not None: + monitor_config["schedule"]["unit"] = schedule_unit + + monitor_config["timezone"] = ( + ( + hasattr(celery_schedule, "tz") + and celery_schedule.tz is not None + and str(celery_schedule.tz) + ) + or app.timezone + or "UTC" + ) + + return monitor_config + + +def _apply_crons_data_to_schedule_entry(scheduler, schedule_entry, integration): + # type: (Any, Any, sentry_sdk.integrations.celery.CeleryIntegration) -> None + """ + Add Sentry Crons information to the schedule_entry headers. + """ + if not integration.monitor_beat_tasks: + return + + monitor_name = schedule_entry.name + + task_should_be_excluded = match_regex_list( + monitor_name, integration.exclude_beat_tasks + ) + if task_should_be_excluded: + return + + celery_schedule = schedule_entry.schedule + app = scheduler.app + + monitor_config = _get_monitor_config(celery_schedule, app, monitor_name) + + is_supported_schedule = bool(monitor_config) + if not is_supported_schedule: + return + + headers = schedule_entry.options.pop("headers", {}) + headers.update( + { + "sentry-monitor-slug": monitor_name, + "sentry-monitor-config": monitor_config, + } + ) + + check_in_id = capture_checkin( + monitor_slug=monitor_name, + monitor_config=monitor_config, + status=MonitorStatus.IN_PROGRESS, + ) + headers.update({"sentry-monitor-check-in-id": check_in_id}) + + # Set the Sentry configuration in the options of the ScheduleEntry. + # Those will be picked up in `apply_async` and added to the headers. + schedule_entry.options["headers"] = headers + + +def _wrap_beat_scheduler(original_function): + # type: (Callable[..., Any]) -> Callable[..., Any] + """ + Makes sure that: + - a new Sentry trace is started for each task started by Celery Beat and + it is propagated to the task. + - the Sentry Crons information is set in the Celery Beat task's + headers so that is is monitored with Sentry Crons. + + After the patched function is called, + Celery Beat will call apply_async to put the task in the queue. + """ + # Patch only once + # Can't use __name__ here, because some of our tests mock original_apply_entry + already_patched = "sentry_patched_scheduler" in str(original_function) + if already_patched: + return original_function + + from sentry_sdk.integrations.celery import CeleryIntegration + + def sentry_patched_scheduler(*args, **kwargs): + # type: (*Any, **Any) -> None + integration = sentry_sdk.get_client().get_integration(CeleryIntegration) + if integration is None: + return original_function(*args, **kwargs) + + # Tasks started by Celery Beat start a new Trace + scope = sentry_sdk.get_isolation_scope() + scope.set_new_propagation_context() + scope._name = "celery-beat" + + scheduler, schedule_entry = args + _apply_crons_data_to_schedule_entry(scheduler, schedule_entry, integration) + + return original_function(*args, **kwargs) + + return sentry_patched_scheduler + + +def _patch_beat_apply_entry(): + # type: () -> None + Scheduler.apply_entry = _wrap_beat_scheduler(Scheduler.apply_entry) + + +def _patch_redbeat_maybe_due(): + # type: () -> None + if RedBeatScheduler is None: + return + + RedBeatScheduler.maybe_due = _wrap_beat_scheduler(RedBeatScheduler.maybe_due) + + +def _setup_celery_beat_signals(monitor_beat_tasks): + # type: (bool) -> None + if monitor_beat_tasks: + task_success.connect(crons_task_success) + task_failure.connect(crons_task_failure) + task_retry.connect(crons_task_retry) + + +def crons_task_success(sender, **kwargs): + # type: (Task, dict[Any, Any]) -> None + logger.debug("celery_task_success %s", sender) + headers = _get_headers(sender) + + if "sentry-monitor-slug" not in headers: + return + + monitor_config = headers.get("sentry-monitor-config", {}) + + start_timestamp_s = headers.get("sentry-monitor-start-timestamp-s") + + capture_checkin( + monitor_slug=headers["sentry-monitor-slug"], + monitor_config=monitor_config, + check_in_id=headers["sentry-monitor-check-in-id"], + duration=( + _now_seconds_since_epoch() - float(start_timestamp_s) + if start_timestamp_s + else None + ), + status=MonitorStatus.OK, + ) + + +def crons_task_failure(sender, **kwargs): + # type: (Task, dict[Any, Any]) -> None + logger.debug("celery_task_failure %s", sender) + headers = _get_headers(sender) + + if "sentry-monitor-slug" not in headers: + return + + monitor_config = headers.get("sentry-monitor-config", {}) + + start_timestamp_s = headers.get("sentry-monitor-start-timestamp-s") + + capture_checkin( + monitor_slug=headers["sentry-monitor-slug"], + monitor_config=monitor_config, + check_in_id=headers["sentry-monitor-check-in-id"], + duration=( + _now_seconds_since_epoch() - float(start_timestamp_s) + if start_timestamp_s + else None + ), + status=MonitorStatus.ERROR, + ) + + +def crons_task_retry(sender, **kwargs): + # type: (Task, dict[Any, Any]) -> None + logger.debug("celery_task_retry %s", sender) + headers = _get_headers(sender) + + if "sentry-monitor-slug" not in headers: + return + + monitor_config = headers.get("sentry-monitor-config", {}) + + start_timestamp_s = headers.get("sentry-monitor-start-timestamp-s") + + capture_checkin( + monitor_slug=headers["sentry-monitor-slug"], + monitor_config=monitor_config, + check_in_id=headers["sentry-monitor-check-in-id"], + duration=( + _now_seconds_since_epoch() - float(start_timestamp_s) + if start_timestamp_s + else None + ), + status=MonitorStatus.ERROR, + ) diff --git a/aws/lambda_demo/sentry_sdk/integrations/celery/utils.py b/aws/lambda_demo/sentry_sdk/integrations/celery/utils.py new file mode 100644 index 000000000..a1961b15b --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/celery/utils.py @@ -0,0 +1,43 @@ +import time +from typing import TYPE_CHECKING, cast + +if TYPE_CHECKING: + from typing import Any, Tuple + from sentry_sdk._types import MonitorConfigScheduleUnit + + +def _now_seconds_since_epoch(): + # type: () -> float + # We cannot use `time.perf_counter()` when dealing with the duration + # of a Celery task, because the start of a Celery task and + # the end are recorded in different processes. + # Start happens in the Celery Beat process, + # the end in a Celery Worker process. + return time.time() + + +def _get_humanized_interval(seconds): + # type: (float) -> Tuple[int, MonitorConfigScheduleUnit] + TIME_UNITS = ( # noqa: N806 + ("day", 60 * 60 * 24.0), + ("hour", 60 * 60.0), + ("minute", 60.0), + ) + + seconds = float(seconds) + for unit, divider in TIME_UNITS: + if seconds >= divider: + interval = int(seconds / divider) + return (interval, cast("MonitorConfigScheduleUnit", unit)) + + return (int(seconds), "second") + + +class NoOpMgr: + def __enter__(self): + # type: () -> None + return None + + def __exit__(self, exc_type, exc_value, traceback): + # type: (Any, Any, Any) -> None + return None diff --git a/aws/lambda_demo/sentry_sdk/integrations/chalice.py b/aws/lambda_demo/sentry_sdk/integrations/chalice.py new file mode 100644 index 000000000..0754d1f13 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/chalice.py @@ -0,0 +1,134 @@ +import sys +from functools import wraps + +import sentry_sdk +from sentry_sdk.integrations import Integration, DidNotEnable +from sentry_sdk.integrations.aws_lambda import _make_request_event_processor +from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT +from sentry_sdk.utils import ( + capture_internal_exceptions, + event_from_exception, + parse_version, + reraise, +) + +try: + import chalice # type: ignore + from chalice import __version__ as CHALICE_VERSION + from chalice import Chalice, ChaliceViewError + from chalice.app import EventSourceHandler as ChaliceEventSourceHandler # type: ignore +except ImportError: + raise DidNotEnable("Chalice is not installed") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Dict + from typing import TypeVar + from typing import Callable + + F = TypeVar("F", bound=Callable[..., Any]) + + +class EventSourceHandler(ChaliceEventSourceHandler): # type: ignore + def __call__(self, event, context): + # type: (Any, Any) -> Any + client = sentry_sdk.get_client() + + with sentry_sdk.isolation_scope() as scope: + with capture_internal_exceptions(): + configured_time = context.get_remaining_time_in_millis() + scope.add_event_processor( + _make_request_event_processor(event, context, configured_time) + ) + try: + return ChaliceEventSourceHandler.__call__(self, event, context) + except Exception: + exc_info = sys.exc_info() + event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + client.flush() + reraise(*exc_info) + + +def _get_view_function_response(app, view_function, function_args): + # type: (Any, F, Any) -> F + @wraps(view_function) + def wrapped_view_function(**function_args): + # type: (**Any) -> Any + client = sentry_sdk.get_client() + with sentry_sdk.isolation_scope() as scope: + with capture_internal_exceptions(): + configured_time = app.lambda_context.get_remaining_time_in_millis() + scope.set_transaction_name( + app.lambda_context.function_name, + source=TRANSACTION_SOURCE_COMPONENT, + ) + + scope.add_event_processor( + _make_request_event_processor( + app.current_request.to_dict(), + app.lambda_context, + configured_time, + ) + ) + try: + return view_function(**function_args) + except Exception as exc: + if isinstance(exc, ChaliceViewError): + raise + exc_info = sys.exc_info() + event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "chalice", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + client.flush() + raise + + return wrapped_view_function # type: ignore + + +class ChaliceIntegration(Integration): + identifier = "chalice" + + @staticmethod + def setup_once(): + # type: () -> None + + version = parse_version(CHALICE_VERSION) + + if version is None: + raise DidNotEnable("Unparsable Chalice version: {}".format(CHALICE_VERSION)) + + if version < (1, 20): + old_get_view_function_response = Chalice._get_view_function_response + else: + from chalice.app import RestAPIEventHandler + + old_get_view_function_response = ( + RestAPIEventHandler._get_view_function_response + ) + + def sentry_event_response(app, view_function, function_args): + # type: (Any, F, Dict[str, Any]) -> Any + wrapped_view_function = _get_view_function_response( + app, view_function, function_args + ) + + return old_get_view_function_response( + app, wrapped_view_function, function_args + ) + + if version < (1, 20): + Chalice._get_view_function_response = sentry_event_response + else: + RestAPIEventHandler._get_view_function_response = sentry_event_response + # for everything else (like events) + chalice.app.EventSourceHandler = EventSourceHandler diff --git a/aws/lambda_demo/sentry_sdk/integrations/clickhouse_driver.py b/aws/lambda_demo/sentry_sdk/integrations/clickhouse_driver.py new file mode 100644 index 000000000..2561bfad0 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/clickhouse_driver.py @@ -0,0 +1,157 @@ +import sentry_sdk +from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable +from sentry_sdk.tracing import Span +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.utils import capture_internal_exceptions, ensure_integration_enabled + +from typing import TYPE_CHECKING, TypeVar + +# Hack to get new Python features working in older versions +# without introducing a hard dependency on `typing_extensions` +# from: https://stackoverflow.com/a/71944042/300572 +if TYPE_CHECKING: + from typing import ParamSpec, Callable +else: + # Fake ParamSpec + class ParamSpec: + def __init__(self, _): + self.args = None + self.kwargs = None + + # Callable[anything] will return None + class _Callable: + def __getitem__(self, _): + return None + + # Make instances + Callable = _Callable() + + +try: + import clickhouse_driver # type: ignore[import-not-found] + +except ImportError: + raise DidNotEnable("clickhouse-driver not installed.") + + +class ClickhouseDriverIntegration(Integration): + identifier = "clickhouse_driver" + origin = f"auto.db.{identifier}" + + @staticmethod + def setup_once() -> None: + _check_minimum_version(ClickhouseDriverIntegration, clickhouse_driver.VERSION) + + # Every query is done using the Connection's `send_query` function + clickhouse_driver.connection.Connection.send_query = _wrap_start( + clickhouse_driver.connection.Connection.send_query + ) + + # If the query contains parameters then the send_data function is used to send those parameters to clickhouse + clickhouse_driver.client.Client.send_data = _wrap_send_data( + clickhouse_driver.client.Client.send_data + ) + + # Every query ends either with the Client's `receive_end_of_query` (no result expected) + # or its `receive_result` (result expected) + clickhouse_driver.client.Client.receive_end_of_query = _wrap_end( + clickhouse_driver.client.Client.receive_end_of_query + ) + if hasattr(clickhouse_driver.client.Client, "receive_end_of_insert_query"): + # In 0.2.7, insert queries are handled separately via `receive_end_of_insert_query` + clickhouse_driver.client.Client.receive_end_of_insert_query = _wrap_end( + clickhouse_driver.client.Client.receive_end_of_insert_query + ) + clickhouse_driver.client.Client.receive_result = _wrap_end( + clickhouse_driver.client.Client.receive_result + ) + + +P = ParamSpec("P") +T = TypeVar("T") + + +def _wrap_start(f: Callable[P, T]) -> Callable[P, T]: + @ensure_integration_enabled(ClickhouseDriverIntegration, f) + def _inner(*args: P.args, **kwargs: P.kwargs) -> T: + connection = args[0] + query = args[1] + query_id = args[2] if len(args) > 2 else kwargs.get("query_id") + params = args[3] if len(args) > 3 else kwargs.get("params") + + span = sentry_sdk.start_span( + op=OP.DB, + name=query, + origin=ClickhouseDriverIntegration.origin, + ) + + connection._sentry_span = span # type: ignore[attr-defined] + + _set_db_data(span, connection) + + span.set_data("query", query) + + if query_id: + span.set_data("db.query_id", query_id) + + if params and should_send_default_pii(): + span.set_data("db.params", params) + + # run the original code + ret = f(*args, **kwargs) + + return ret + + return _inner + + +def _wrap_end(f: Callable[P, T]) -> Callable[P, T]: + def _inner_end(*args: P.args, **kwargs: P.kwargs) -> T: + res = f(*args, **kwargs) + instance = args[0] + span = getattr(instance.connection, "_sentry_span", None) # type: ignore[attr-defined] + + if span is not None: + if res is not None and should_send_default_pii(): + span.set_data("db.result", res) + + with capture_internal_exceptions(): + span.scope.add_breadcrumb( + message=span._data.pop("query"), category="query", data=span._data + ) + + span.finish() + + return res + + return _inner_end + + +def _wrap_send_data(f: Callable[P, T]) -> Callable[P, T]: + def _inner_send_data(*args: P.args, **kwargs: P.kwargs) -> T: + instance = args[0] # type: clickhouse_driver.client.Client + data = args[2] + span = getattr(instance.connection, "_sentry_span", None) + + if span is not None: + _set_db_data(span, instance.connection) + + if should_send_default_pii(): + db_params = span._data.get("db.params", []) + db_params.extend(data) + span.set_data("db.params", db_params) + + return f(*args, **kwargs) + + return _inner_send_data + + +def _set_db_data( + span: Span, connection: clickhouse_driver.connection.Connection +) -> None: + span.set_data(SPANDATA.DB_SYSTEM, "clickhouse") + span.set_data(SPANDATA.SERVER_ADDRESS, connection.host) + span.set_data(SPANDATA.SERVER_PORT, connection.port) + span.set_data(SPANDATA.DB_NAME, connection.database) + span.set_data(SPANDATA.DB_USER, connection.user) diff --git a/aws/lambda_demo/sentry_sdk/integrations/cloud_resource_context.py b/aws/lambda_demo/sentry_sdk/integrations/cloud_resource_context.py new file mode 100644 index 000000000..8d080899f --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/cloud_resource_context.py @@ -0,0 +1,258 @@ +import json +import urllib3 + +from sentry_sdk.integrations import Integration +from sentry_sdk.api import set_context +from sentry_sdk.utils import logger + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Dict + + +CONTEXT_TYPE = "cloud_resource" + +AWS_METADATA_HOST = "169.254.169.254" +AWS_TOKEN_URL = "http://{}/latest/api/token".format(AWS_METADATA_HOST) +AWS_METADATA_URL = "http://{}/latest/dynamic/instance-identity/document".format( + AWS_METADATA_HOST +) + +GCP_METADATA_HOST = "metadata.google.internal" +GCP_METADATA_URL = "http://{}/computeMetadata/v1/?recursive=true".format( + GCP_METADATA_HOST +) + + +class CLOUD_PROVIDER: # noqa: N801 + """ + Name of the cloud provider. + see https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/cloud/ + """ + + ALIBABA = "alibaba_cloud" + AWS = "aws" + AZURE = "azure" + GCP = "gcp" + IBM = "ibm_cloud" + TENCENT = "tencent_cloud" + + +class CLOUD_PLATFORM: # noqa: N801 + """ + The cloud platform. + see https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/cloud/ + """ + + AWS_EC2 = "aws_ec2" + GCP_COMPUTE_ENGINE = "gcp_compute_engine" + + +class CloudResourceContextIntegration(Integration): + """ + Adds cloud resource context to the Senty scope + """ + + identifier = "cloudresourcecontext" + + cloud_provider = "" + + aws_token = "" + http = urllib3.PoolManager() + + gcp_metadata = None + + def __init__(self, cloud_provider=""): + # type: (str) -> None + CloudResourceContextIntegration.cloud_provider = cloud_provider + + @classmethod + def _is_aws(cls): + # type: () -> bool + try: + r = cls.http.request( + "PUT", + AWS_TOKEN_URL, + headers={"X-aws-ec2-metadata-token-ttl-seconds": "60"}, + ) + + if r.status != 200: + return False + + cls.aws_token = r.data.decode() + return True + + except Exception: + return False + + @classmethod + def _get_aws_context(cls): + # type: () -> Dict[str, str] + ctx = { + "cloud.provider": CLOUD_PROVIDER.AWS, + "cloud.platform": CLOUD_PLATFORM.AWS_EC2, + } + + try: + r = cls.http.request( + "GET", + AWS_METADATA_URL, + headers={"X-aws-ec2-metadata-token": cls.aws_token}, + ) + + if r.status != 200: + return ctx + + data = json.loads(r.data.decode("utf-8")) + + try: + ctx["cloud.account.id"] = data["accountId"] + except Exception: + pass + + try: + ctx["cloud.availability_zone"] = data["availabilityZone"] + except Exception: + pass + + try: + ctx["cloud.region"] = data["region"] + except Exception: + pass + + try: + ctx["host.id"] = data["instanceId"] + except Exception: + pass + + try: + ctx["host.type"] = data["instanceType"] + except Exception: + pass + + except Exception: + pass + + return ctx + + @classmethod + def _is_gcp(cls): + # type: () -> bool + try: + r = cls.http.request( + "GET", + GCP_METADATA_URL, + headers={"Metadata-Flavor": "Google"}, + ) + + if r.status != 200: + return False + + cls.gcp_metadata = json.loads(r.data.decode("utf-8")) + return True + + except Exception: + return False + + @classmethod + def _get_gcp_context(cls): + # type: () -> Dict[str, str] + ctx = { + "cloud.provider": CLOUD_PROVIDER.GCP, + "cloud.platform": CLOUD_PLATFORM.GCP_COMPUTE_ENGINE, + } + + try: + if cls.gcp_metadata is None: + r = cls.http.request( + "GET", + GCP_METADATA_URL, + headers={"Metadata-Flavor": "Google"}, + ) + + if r.status != 200: + return ctx + + cls.gcp_metadata = json.loads(r.data.decode("utf-8")) + + try: + ctx["cloud.account.id"] = cls.gcp_metadata["project"]["projectId"] + except Exception: + pass + + try: + ctx["cloud.availability_zone"] = cls.gcp_metadata["instance"][ + "zone" + ].split("/")[-1] + except Exception: + pass + + try: + # only populated in google cloud run + ctx["cloud.region"] = cls.gcp_metadata["instance"]["region"].split("/")[ + -1 + ] + except Exception: + pass + + try: + ctx["host.id"] = cls.gcp_metadata["instance"]["id"] + except Exception: + pass + + except Exception: + pass + + return ctx + + @classmethod + def _get_cloud_provider(cls): + # type: () -> str + if cls._is_aws(): + return CLOUD_PROVIDER.AWS + + if cls._is_gcp(): + return CLOUD_PROVIDER.GCP + + return "" + + @classmethod + def _get_cloud_resource_context(cls): + # type: () -> Dict[str, str] + cloud_provider = ( + cls.cloud_provider + if cls.cloud_provider != "" + else CloudResourceContextIntegration._get_cloud_provider() + ) + if cloud_provider in context_getters.keys(): + return context_getters[cloud_provider]() + + return {} + + @staticmethod + def setup_once(): + # type: () -> None + cloud_provider = CloudResourceContextIntegration.cloud_provider + unsupported_cloud_provider = ( + cloud_provider != "" and cloud_provider not in context_getters.keys() + ) + + if unsupported_cloud_provider: + logger.warning( + "Invalid value for cloud_provider: %s (must be in %s). Falling back to autodetection...", + CloudResourceContextIntegration.cloud_provider, + list(context_getters.keys()), + ) + + context = CloudResourceContextIntegration._get_cloud_resource_context() + if context != {}: + set_context(CONTEXT_TYPE, context) + + +# Map with the currently supported cloud providers +# mapping to functions extracting the context +context_getters = { + CLOUD_PROVIDER.AWS: CloudResourceContextIntegration._get_aws_context, + CLOUD_PROVIDER.GCP: CloudResourceContextIntegration._get_gcp_context, +} diff --git a/aws/lambda_demo/sentry_sdk/integrations/cohere.py b/aws/lambda_demo/sentry_sdk/integrations/cohere.py new file mode 100644 index 000000000..b4c2af91d --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/cohere.py @@ -0,0 +1,270 @@ +from functools import wraps + +from sentry_sdk import consts +from sentry_sdk.ai.monitoring import record_token_usage +from sentry_sdk.consts import SPANDATA +from sentry_sdk.ai.utils import set_data_normalized + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Callable, Iterator + from sentry_sdk.tracing import Span + +import sentry_sdk +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.utils import capture_internal_exceptions, event_from_exception + +try: + from cohere.client import Client + from cohere.base_client import BaseCohere + from cohere import ( + ChatStreamEndEvent, + NonStreamedChatResponse, + ) + + if TYPE_CHECKING: + from cohere import StreamedChatResponse +except ImportError: + raise DidNotEnable("Cohere not installed") + +try: + # cohere 5.9.3+ + from cohere import StreamEndStreamedChatResponse +except ImportError: + from cohere import StreamedChatResponse_StreamEnd as StreamEndStreamedChatResponse + + +COLLECTED_CHAT_PARAMS = { + "model": SPANDATA.AI_MODEL_ID, + "k": SPANDATA.AI_TOP_K, + "p": SPANDATA.AI_TOP_P, + "seed": SPANDATA.AI_SEED, + "frequency_penalty": SPANDATA.AI_FREQUENCY_PENALTY, + "presence_penalty": SPANDATA.AI_PRESENCE_PENALTY, + "raw_prompting": SPANDATA.AI_RAW_PROMPTING, +} + +COLLECTED_PII_CHAT_PARAMS = { + "tools": SPANDATA.AI_TOOLS, + "preamble": SPANDATA.AI_PREAMBLE, +} + +COLLECTED_CHAT_RESP_ATTRS = { + "generation_id": "ai.generation_id", + "is_search_required": "ai.is_search_required", + "finish_reason": "ai.finish_reason", +} + +COLLECTED_PII_CHAT_RESP_ATTRS = { + "citations": "ai.citations", + "documents": "ai.documents", + "search_queries": "ai.search_queries", + "search_results": "ai.search_results", + "tool_calls": "ai.tool_calls", +} + + +class CohereIntegration(Integration): + identifier = "cohere" + origin = f"auto.ai.{identifier}" + + def __init__(self, include_prompts=True): + # type: (CohereIntegration, bool) -> None + self.include_prompts = include_prompts + + @staticmethod + def setup_once(): + # type: () -> None + BaseCohere.chat = _wrap_chat(BaseCohere.chat, streaming=False) + Client.embed = _wrap_embed(Client.embed) + BaseCohere.chat_stream = _wrap_chat(BaseCohere.chat_stream, streaming=True) + + +def _capture_exception(exc): + # type: (Any) -> None + event, hint = event_from_exception( + exc, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "cohere", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + +def _wrap_chat(f, streaming): + # type: (Callable[..., Any], bool) -> Callable[..., Any] + + def collect_chat_response_fields(span, res, include_pii): + # type: (Span, NonStreamedChatResponse, bool) -> None + if include_pii: + if hasattr(res, "text"): + set_data_normalized( + span, + SPANDATA.AI_RESPONSES, + [res.text], + ) + for pii_attr in COLLECTED_PII_CHAT_RESP_ATTRS: + if hasattr(res, pii_attr): + set_data_normalized(span, "ai." + pii_attr, getattr(res, pii_attr)) + + for attr in COLLECTED_CHAT_RESP_ATTRS: + if hasattr(res, attr): + set_data_normalized(span, "ai." + attr, getattr(res, attr)) + + if hasattr(res, "meta"): + if hasattr(res.meta, "billed_units"): + record_token_usage( + span, + prompt_tokens=res.meta.billed_units.input_tokens, + completion_tokens=res.meta.billed_units.output_tokens, + ) + elif hasattr(res.meta, "tokens"): + record_token_usage( + span, + prompt_tokens=res.meta.tokens.input_tokens, + completion_tokens=res.meta.tokens.output_tokens, + ) + + if hasattr(res.meta, "warnings"): + set_data_normalized(span, "ai.warnings", res.meta.warnings) + + @wraps(f) + def new_chat(*args, **kwargs): + # type: (*Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(CohereIntegration) + + if ( + integration is None + or "message" not in kwargs + or not isinstance(kwargs.get("message"), str) + ): + return f(*args, **kwargs) + + message = kwargs.get("message") + + span = sentry_sdk.start_span( + op=consts.OP.COHERE_CHAT_COMPLETIONS_CREATE, + name="cohere.client.Chat", + origin=CohereIntegration.origin, + ) + span.__enter__() + try: + res = f(*args, **kwargs) + except Exception as e: + _capture_exception(e) + span.__exit__(None, None, None) + raise e from None + + with capture_internal_exceptions(): + if should_send_default_pii() and integration.include_prompts: + set_data_normalized( + span, + SPANDATA.AI_INPUT_MESSAGES, + list( + map( + lambda x: { + "role": getattr(x, "role", "").lower(), + "content": getattr(x, "message", ""), + }, + kwargs.get("chat_history", []), + ) + ) + + [{"role": "user", "content": message}], + ) + for k, v in COLLECTED_PII_CHAT_PARAMS.items(): + if k in kwargs: + set_data_normalized(span, v, kwargs[k]) + + for k, v in COLLECTED_CHAT_PARAMS.items(): + if k in kwargs: + set_data_normalized(span, v, kwargs[k]) + set_data_normalized(span, SPANDATA.AI_STREAMING, False) + + if streaming: + old_iterator = res + + def new_iterator(): + # type: () -> Iterator[StreamedChatResponse] + + with capture_internal_exceptions(): + for x in old_iterator: + if isinstance(x, ChatStreamEndEvent) or isinstance( + x, StreamEndStreamedChatResponse + ): + collect_chat_response_fields( + span, + x.response, + include_pii=should_send_default_pii() + and integration.include_prompts, + ) + yield x + + span.__exit__(None, None, None) + + return new_iterator() + elif isinstance(res, NonStreamedChatResponse): + collect_chat_response_fields( + span, + res, + include_pii=should_send_default_pii() + and integration.include_prompts, + ) + span.__exit__(None, None, None) + else: + set_data_normalized(span, "unknown_response", True) + span.__exit__(None, None, None) + return res + + return new_chat + + +def _wrap_embed(f): + # type: (Callable[..., Any]) -> Callable[..., Any] + + @wraps(f) + def new_embed(*args, **kwargs): + # type: (*Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(CohereIntegration) + if integration is None: + return f(*args, **kwargs) + + with sentry_sdk.start_span( + op=consts.OP.COHERE_EMBEDDINGS_CREATE, + name="Cohere Embedding Creation", + origin=CohereIntegration.origin, + ) as span: + if "texts" in kwargs and ( + should_send_default_pii() and integration.include_prompts + ): + if isinstance(kwargs["texts"], str): + set_data_normalized(span, "ai.texts", [kwargs["texts"]]) + elif ( + isinstance(kwargs["texts"], list) + and len(kwargs["texts"]) > 0 + and isinstance(kwargs["texts"][0], str) + ): + set_data_normalized( + span, SPANDATA.AI_INPUT_MESSAGES, kwargs["texts"] + ) + + if "model" in kwargs: + set_data_normalized(span, SPANDATA.AI_MODEL_ID, kwargs["model"]) + try: + res = f(*args, **kwargs) + except Exception as e: + _capture_exception(e) + raise e from None + if ( + hasattr(res, "meta") + and hasattr(res.meta, "billed_units") + and hasattr(res.meta.billed_units, "input_tokens") + ): + record_token_usage( + span, + prompt_tokens=res.meta.billed_units.input_tokens, + total_tokens=res.meta.billed_units.input_tokens, + ) + return res + + return new_embed diff --git a/aws/lambda_demo/sentry_sdk/integrations/dedupe.py b/aws/lambda_demo/sentry_sdk/integrations/dedupe.py new file mode 100644 index 000000000..be6d9311a --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/dedupe.py @@ -0,0 +1,42 @@ +import sentry_sdk +from sentry_sdk.utils import ContextVar +from sentry_sdk.integrations import Integration +from sentry_sdk.scope import add_global_event_processor + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Optional + + from sentry_sdk._types import Event, Hint + + +class DedupeIntegration(Integration): + identifier = "dedupe" + + def __init__(self): + # type: () -> None + self._last_seen = ContextVar("last-seen") + + @staticmethod + def setup_once(): + # type: () -> None + @add_global_event_processor + def processor(event, hint): + # type: (Event, Optional[Hint]) -> Optional[Event] + if hint is None: + return event + + integration = sentry_sdk.get_client().get_integration(DedupeIntegration) + if integration is None: + return event + + exc_info = hint.get("exc_info", None) + if exc_info is None: + return event + + exc = exc_info[1] + if integration._last_seen.get(None) is exc: + return None + integration._last_seen.set(exc) + return event diff --git a/aws/lambda_demo/sentry_sdk/integrations/django/__init__.py b/aws/lambda_demo/sentry_sdk/integrations/django/__init__.py new file mode 100644 index 000000000..54bc25675 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/django/__init__.py @@ -0,0 +1,747 @@ +import inspect +import sys +import threading +import weakref +from importlib import import_module + +import sentry_sdk +from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.scope import add_global_event_processor, should_send_default_pii +from sentry_sdk.serializer import add_global_repr_processor +from sentry_sdk.tracing import SOURCE_FOR_STYLE, TRANSACTION_SOURCE_URL +from sentry_sdk.tracing_utils import add_query_source, record_sql_queries +from sentry_sdk.utils import ( + AnnotatedValue, + HAS_REAL_CONTEXTVARS, + CONTEXTVARS_ERROR_MESSAGE, + SENSITIVE_DATA_SUBSTITUTE, + logger, + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + transaction_from_function, + walk_exception_chain, +) +from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable +from sentry_sdk.integrations.logging import ignore_logger +from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware +from sentry_sdk.integrations._wsgi_common import ( + DEFAULT_HTTP_METHODS_TO_CAPTURE, + RequestExtractor, +) + +try: + from django import VERSION as DJANGO_VERSION + from django.conf import settings as django_settings + from django.core import signals + from django.conf import settings + + try: + from django.urls import resolve + except ImportError: + from django.core.urlresolvers import resolve + + try: + from django.urls import Resolver404 + except ImportError: + from django.core.urlresolvers import Resolver404 + + # Only available in Django 3.0+ + try: + from django.core.handlers.asgi import ASGIRequest + except Exception: + ASGIRequest = None + +except ImportError: + raise DidNotEnable("Django not installed") + +from sentry_sdk.integrations.django.transactions import LEGACY_RESOLVER +from sentry_sdk.integrations.django.templates import ( + get_template_frame_from_exception, + patch_templates, +) +from sentry_sdk.integrations.django.middleware import patch_django_middlewares +from sentry_sdk.integrations.django.signals_handlers import patch_signals +from sentry_sdk.integrations.django.views import patch_views + +if DJANGO_VERSION[:2] > (1, 8): + from sentry_sdk.integrations.django.caching import patch_caching +else: + patch_caching = None # type: ignore + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import Dict + from typing import Optional + from typing import Union + from typing import List + + from django.core.handlers.wsgi import WSGIRequest + from django.http.response import HttpResponse + from django.http.request import QueryDict + from django.utils.datastructures import MultiValueDict + + from sentry_sdk.tracing import Span + from sentry_sdk.integrations.wsgi import _ScopedResponse + from sentry_sdk._types import Event, Hint, EventProcessor, NotImplementedType + + +if DJANGO_VERSION < (1, 10): + + def is_authenticated(request_user): + # type: (Any) -> bool + return request_user.is_authenticated() + +else: + + def is_authenticated(request_user): + # type: (Any) -> bool + return request_user.is_authenticated + + +TRANSACTION_STYLE_VALUES = ("function_name", "url") + + +class DjangoIntegration(Integration): + """ + Auto instrument a Django application. + + :param transaction_style: How to derive transaction names. Either `"function_name"` or `"url"`. Defaults to `"url"`. + :param middleware_spans: Whether to create spans for middleware. Defaults to `True`. + :param signals_spans: Whether to create spans for signals. Defaults to `True`. + :param signals_denylist: A list of signals to ignore when creating spans. + :param cache_spans: Whether to create spans for cache operations. Defaults to `False`. + """ + + identifier = "django" + origin = f"auto.http.{identifier}" + origin_db = f"auto.db.{identifier}" + + transaction_style = "" + middleware_spans = None + signals_spans = None + cache_spans = None + signals_denylist = [] # type: list[signals.Signal] + + def __init__( + self, + transaction_style="url", # type: str + middleware_spans=True, # type: bool + signals_spans=True, # type: bool + cache_spans=False, # type: bool + signals_denylist=None, # type: Optional[list[signals.Signal]] + http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: tuple[str, ...] + ): + # type: (...) -> None + if transaction_style not in TRANSACTION_STYLE_VALUES: + raise ValueError( + "Invalid value for transaction_style: %s (must be in %s)" + % (transaction_style, TRANSACTION_STYLE_VALUES) + ) + self.transaction_style = transaction_style + self.middleware_spans = middleware_spans + + self.signals_spans = signals_spans + self.signals_denylist = signals_denylist or [] + + self.cache_spans = cache_spans + + self.http_methods_to_capture = tuple(map(str.upper, http_methods_to_capture)) + + @staticmethod + def setup_once(): + # type: () -> None + _check_minimum_version(DjangoIntegration, DJANGO_VERSION) + + install_sql_hook() + # Patch in our custom middleware. + + # logs an error for every 500 + ignore_logger("django.server") + ignore_logger("django.request") + + from django.core.handlers.wsgi import WSGIHandler + + old_app = WSGIHandler.__call__ + + @ensure_integration_enabled(DjangoIntegration, old_app) + def sentry_patched_wsgi_handler(self, environ, start_response): + # type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse + bound_old_app = old_app.__get__(self, WSGIHandler) + + from django.conf import settings + + use_x_forwarded_for = settings.USE_X_FORWARDED_HOST + + integration = sentry_sdk.get_client().get_integration(DjangoIntegration) + + middleware = SentryWsgiMiddleware( + bound_old_app, + use_x_forwarded_for, + span_origin=DjangoIntegration.origin, + http_methods_to_capture=( + integration.http_methods_to_capture + if integration + else DEFAULT_HTTP_METHODS_TO_CAPTURE + ), + ) + return middleware(environ, start_response) + + WSGIHandler.__call__ = sentry_patched_wsgi_handler + + _patch_get_response() + + _patch_django_asgi_handler() + + signals.got_request_exception.connect(_got_request_exception) + + @add_global_event_processor + def process_django_templates(event, hint): + # type: (Event, Optional[Hint]) -> Optional[Event] + if hint is None: + return event + + exc_info = hint.get("exc_info", None) + + if exc_info is None: + return event + + exception = event.get("exception", None) + + if exception is None: + return event + + values = exception.get("values", None) + + if values is None: + return event + + for exception, (_, exc_value, _) in zip( + reversed(values), walk_exception_chain(exc_info) + ): + frame = get_template_frame_from_exception(exc_value) + if frame is not None: + frames = exception.get("stacktrace", {}).get("frames", []) + + for i in reversed(range(len(frames))): + f = frames[i] + if ( + f.get("function") in ("Parser.parse", "parse", "render") + and f.get("module") == "django.template.base" + ): + i += 1 + break + else: + i = len(frames) + + frames.insert(i, frame) + + return event + + @add_global_repr_processor + def _django_queryset_repr(value, hint): + # type: (Any, Dict[str, Any]) -> Union[NotImplementedType, str] + try: + # Django 1.6 can fail to import `QuerySet` when Django settings + # have not yet been initialized. + # + # If we fail to import, return `NotImplemented`. It's at least + # unlikely that we have a query set in `value` when importing + # `QuerySet` fails. + from django.db.models.query import QuerySet + except Exception: + return NotImplemented + + if not isinstance(value, QuerySet) or value._result_cache: + return NotImplemented + + return "<%s from %s at 0x%x>" % ( + value.__class__.__name__, + value.__module__, + id(value), + ) + + _patch_channels() + patch_django_middlewares() + patch_views() + patch_templates() + patch_signals() + + if patch_caching is not None: + patch_caching() + + +_DRF_PATCHED = False +_DRF_PATCH_LOCK = threading.Lock() + + +def _patch_drf(): + # type: () -> None + """ + Patch Django Rest Framework for more/better request data. DRF's request + type is a wrapper around Django's request type. The attribute we're + interested in is `request.data`, which is a cached property containing a + parsed request body. Reading a request body from that property is more + reliable than reading from any of Django's own properties, as those don't + hold payloads in memory and therefore can only be accessed once. + + We patch the Django request object to include a weak backreference to the + DRF request object, such that we can later use either in + `DjangoRequestExtractor`. + + This function is not called directly on SDK setup, because importing almost + any part of Django Rest Framework will try to access Django settings (where + `sentry_sdk.init()` might be called from in the first place). Instead we + run this function on every request and do the patching on the first + request. + """ + + global _DRF_PATCHED + + if _DRF_PATCHED: + # Double-checked locking + return + + with _DRF_PATCH_LOCK: + if _DRF_PATCHED: + return + + # We set this regardless of whether the code below succeeds or fails. + # There is no point in trying to patch again on the next request. + _DRF_PATCHED = True + + with capture_internal_exceptions(): + try: + from rest_framework.views import APIView # type: ignore + except ImportError: + pass + else: + old_drf_initial = APIView.initial + + def sentry_patched_drf_initial(self, request, *args, **kwargs): + # type: (APIView, Any, *Any, **Any) -> Any + with capture_internal_exceptions(): + request._request._sentry_drf_request_backref = weakref.ref( + request + ) + pass + return old_drf_initial(self, request, *args, **kwargs) + + APIView.initial = sentry_patched_drf_initial + + +def _patch_channels(): + # type: () -> None + try: + from channels.http import AsgiHandler # type: ignore + except ImportError: + return + + if not HAS_REAL_CONTEXTVARS: + # We better have contextvars or we're going to leak state between + # requests. + # + # We cannot hard-raise here because channels may not be used at all in + # the current process. That is the case when running traditional WSGI + # workers in gunicorn+gevent and the websocket stuff in a separate + # process. + logger.warning( + "We detected that you are using Django channels 2.0." + + CONTEXTVARS_ERROR_MESSAGE + ) + + from sentry_sdk.integrations.django.asgi import patch_channels_asgi_handler_impl + + patch_channels_asgi_handler_impl(AsgiHandler) + + +def _patch_django_asgi_handler(): + # type: () -> None + try: + from django.core.handlers.asgi import ASGIHandler + except ImportError: + return + + if not HAS_REAL_CONTEXTVARS: + # We better have contextvars or we're going to leak state between + # requests. + # + # We cannot hard-raise here because Django's ASGI stuff may not be used + # at all. + logger.warning( + "We detected that you are using Django 3." + CONTEXTVARS_ERROR_MESSAGE + ) + + from sentry_sdk.integrations.django.asgi import patch_django_asgi_handler_impl + + patch_django_asgi_handler_impl(ASGIHandler) + + +def _set_transaction_name_and_source(scope, transaction_style, request): + # type: (sentry_sdk.Scope, str, WSGIRequest) -> None + try: + transaction_name = None + if transaction_style == "function_name": + fn = resolve(request.path).func + transaction_name = transaction_from_function(getattr(fn, "view_class", fn)) + + elif transaction_style == "url": + if hasattr(request, "urlconf"): + transaction_name = LEGACY_RESOLVER.resolve( + request.path_info, urlconf=request.urlconf + ) + else: + transaction_name = LEGACY_RESOLVER.resolve(request.path_info) + + if transaction_name is None: + transaction_name = request.path_info + source = TRANSACTION_SOURCE_URL + else: + source = SOURCE_FOR_STYLE[transaction_style] + + scope.set_transaction_name( + transaction_name, + source=source, + ) + except Resolver404: + urlconf = import_module(settings.ROOT_URLCONF) + # This exception only gets thrown when transaction_style is `function_name` + # So we don't check here what style is configured + if hasattr(urlconf, "handler404"): + handler = urlconf.handler404 + if isinstance(handler, str): + scope.transaction = handler + else: + scope.transaction = transaction_from_function( + getattr(handler, "view_class", handler) + ) + except Exception: + pass + + +def _before_get_response(request): + # type: (WSGIRequest) -> None + integration = sentry_sdk.get_client().get_integration(DjangoIntegration) + if integration is None: + return + + _patch_drf() + + scope = sentry_sdk.get_current_scope() + # Rely on WSGI middleware to start a trace + _set_transaction_name_and_source(scope, integration.transaction_style, request) + + scope.add_event_processor( + _make_wsgi_request_event_processor(weakref.ref(request), integration) + ) + + +def _attempt_resolve_again(request, scope, transaction_style): + # type: (WSGIRequest, sentry_sdk.Scope, str) -> None + """ + Some django middlewares overwrite request.urlconf + so we need to respect that contract, + so we try to resolve the url again. + """ + if not hasattr(request, "urlconf"): + return + + _set_transaction_name_and_source(scope, transaction_style, request) + + +def _after_get_response(request): + # type: (WSGIRequest) -> None + integration = sentry_sdk.get_client().get_integration(DjangoIntegration) + if integration is None or integration.transaction_style != "url": + return + + scope = sentry_sdk.get_current_scope() + _attempt_resolve_again(request, scope, integration.transaction_style) + + +def _patch_get_response(): + # type: () -> None + """ + patch get_response, because at that point we have the Django request object + """ + from django.core.handlers.base import BaseHandler + + old_get_response = BaseHandler.get_response + + def sentry_patched_get_response(self, request): + # type: (Any, WSGIRequest) -> Union[HttpResponse, BaseException] + _before_get_response(request) + rv = old_get_response(self, request) + _after_get_response(request) + return rv + + BaseHandler.get_response = sentry_patched_get_response + + if hasattr(BaseHandler, "get_response_async"): + from sentry_sdk.integrations.django.asgi import patch_get_response_async + + patch_get_response_async(BaseHandler, _before_get_response) + + +def _make_wsgi_request_event_processor(weak_request, integration): + # type: (Callable[[], WSGIRequest], DjangoIntegration) -> EventProcessor + def wsgi_request_event_processor(event, hint): + # type: (Event, dict[str, Any]) -> Event + # if the request is gone we are fine not logging the data from + # it. This might happen if the processor is pushed away to + # another thread. + request = weak_request() + if request is None: + return event + + django_3 = ASGIRequest is not None + if django_3 and type(request) == ASGIRequest: + # We have a `asgi_request_event_processor` for this. + return event + + with capture_internal_exceptions(): + DjangoRequestExtractor(request).extract_into_event(event) + + if should_send_default_pii(): + with capture_internal_exceptions(): + _set_user_info(request, event) + + return event + + return wsgi_request_event_processor + + +def _got_request_exception(request=None, **kwargs): + # type: (WSGIRequest, **Any) -> None + client = sentry_sdk.get_client() + integration = client.get_integration(DjangoIntegration) + if integration is None: + return + + if request is not None and integration.transaction_style == "url": + scope = sentry_sdk.get_current_scope() + _attempt_resolve_again(request, scope, integration.transaction_style) + + event, hint = event_from_exception( + sys.exc_info(), + client_options=client.options, + mechanism={"type": "django", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + +class DjangoRequestExtractor(RequestExtractor): + def __init__(self, request): + # type: (Union[WSGIRequest, ASGIRequest]) -> None + try: + drf_request = request._sentry_drf_request_backref() + if drf_request is not None: + request = drf_request + except AttributeError: + pass + self.request = request + + def env(self): + # type: () -> Dict[str, str] + return self.request.META + + def cookies(self): + # type: () -> Dict[str, Union[str, AnnotatedValue]] + privacy_cookies = [ + django_settings.CSRF_COOKIE_NAME, + django_settings.SESSION_COOKIE_NAME, + ] + + clean_cookies = {} # type: Dict[str, Union[str, AnnotatedValue]] + for key, val in self.request.COOKIES.items(): + if key in privacy_cookies: + clean_cookies[key] = SENSITIVE_DATA_SUBSTITUTE + else: + clean_cookies[key] = val + + return clean_cookies + + def raw_data(self): + # type: () -> bytes + return self.request.body + + def form(self): + # type: () -> QueryDict + return self.request.POST + + def files(self): + # type: () -> MultiValueDict + return self.request.FILES + + def size_of_file(self, file): + # type: (Any) -> int + return file.size + + def parsed_body(self): + # type: () -> Optional[Dict[str, Any]] + try: + return self.request.data + except AttributeError: + return RequestExtractor.parsed_body(self) + + +def _set_user_info(request, event): + # type: (WSGIRequest, Event) -> None + user_info = event.setdefault("user", {}) + + user = getattr(request, "user", None) + + if user is None or not is_authenticated(user): + return + + try: + user_info.setdefault("id", str(user.pk)) + except Exception: + pass + + try: + user_info.setdefault("email", user.email) + except Exception: + pass + + try: + user_info.setdefault("username", user.get_username()) + except Exception: + pass + + +def install_sql_hook(): + # type: () -> None + """If installed this causes Django's queries to be captured.""" + try: + from django.db.backends.utils import CursorWrapper + except ImportError: + from django.db.backends.util import CursorWrapper + + try: + # django 1.6 and 1.7 compatability + from django.db.backends import BaseDatabaseWrapper + except ImportError: + # django 1.8 or later + from django.db.backends.base.base import BaseDatabaseWrapper + + try: + real_execute = CursorWrapper.execute + real_executemany = CursorWrapper.executemany + real_connect = BaseDatabaseWrapper.connect + except AttributeError: + # This won't work on Django versions < 1.6 + return + + @ensure_integration_enabled(DjangoIntegration, real_execute) + def execute(self, sql, params=None): + # type: (CursorWrapper, Any, Optional[Any]) -> Any + with record_sql_queries( + cursor=self.cursor, + query=sql, + params_list=params, + paramstyle="format", + executemany=False, + span_origin=DjangoIntegration.origin_db, + ) as span: + _set_db_data(span, self) + result = real_execute(self, sql, params) + + with capture_internal_exceptions(): + add_query_source(span) + + return result + + @ensure_integration_enabled(DjangoIntegration, real_executemany) + def executemany(self, sql, param_list): + # type: (CursorWrapper, Any, List[Any]) -> Any + with record_sql_queries( + cursor=self.cursor, + query=sql, + params_list=param_list, + paramstyle="format", + executemany=True, + span_origin=DjangoIntegration.origin_db, + ) as span: + _set_db_data(span, self) + + result = real_executemany(self, sql, param_list) + + with capture_internal_exceptions(): + add_query_source(span) + + return result + + @ensure_integration_enabled(DjangoIntegration, real_connect) + def connect(self): + # type: (BaseDatabaseWrapper) -> None + with capture_internal_exceptions(): + sentry_sdk.add_breadcrumb(message="connect", category="query") + + with sentry_sdk.start_span( + op=OP.DB, + name="connect", + origin=DjangoIntegration.origin_db, + ) as span: + _set_db_data(span, self) + return real_connect(self) + + CursorWrapper.execute = execute + CursorWrapper.executemany = executemany + BaseDatabaseWrapper.connect = connect + ignore_logger("django.db.backends") + + +def _set_db_data(span, cursor_or_db): + # type: (Span, Any) -> None + db = cursor_or_db.db if hasattr(cursor_or_db, "db") else cursor_or_db + vendor = db.vendor + span.set_data(SPANDATA.DB_SYSTEM, vendor) + + # Some custom backends override `__getattr__`, making it look like `cursor_or_db` + # actually has a `connection` and the `connection` has a `get_dsn_parameters` + # attribute, only to throw an error once you actually want to call it. + # Hence the `inspect` check whether `get_dsn_parameters` is an actual callable + # function. + is_psycopg2 = ( + hasattr(cursor_or_db, "connection") + and hasattr(cursor_or_db.connection, "get_dsn_parameters") + and inspect.isroutine(cursor_or_db.connection.get_dsn_parameters) + ) + if is_psycopg2: + connection_params = cursor_or_db.connection.get_dsn_parameters() + else: + try: + # psycopg3, only extract needed params as get_parameters + # can be slow because of the additional logic to filter out default + # values + connection_params = { + "dbname": cursor_or_db.connection.info.dbname, + "port": cursor_or_db.connection.info.port, + } + # PGhost returns host or base dir of UNIX socket as an absolute path + # starting with /, use it only when it contains host + pg_host = cursor_or_db.connection.info.host + if pg_host and not pg_host.startswith("/"): + connection_params["host"] = pg_host + except Exception: + connection_params = db.get_connection_params() + + db_name = connection_params.get("dbname") or connection_params.get("database") + if db_name is not None: + span.set_data(SPANDATA.DB_NAME, db_name) + + server_address = connection_params.get("host") + if server_address is not None: + span.set_data(SPANDATA.SERVER_ADDRESS, server_address) + + server_port = connection_params.get("port") + if server_port is not None: + span.set_data(SPANDATA.SERVER_PORT, str(server_port)) + + server_socket_address = connection_params.get("unix_socket") + if server_socket_address is not None: + span.set_data(SPANDATA.SERVER_SOCKET_ADDRESS, server_socket_address) diff --git a/aws/lambda_demo/sentry_sdk/integrations/django/asgi.py b/aws/lambda_demo/sentry_sdk/integrations/django/asgi.py new file mode 100644 index 000000000..73a25acc9 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/django/asgi.py @@ -0,0 +1,245 @@ +""" +Instrumentation for Django 3.0 + +Since this file contains `async def` it is conditionally imported in +`sentry_sdk.integrations.django` (depending on the existence of +`django.core.handlers.asgi`. +""" + +import asyncio +import functools +import inspect + +from django.core.handlers.wsgi import WSGIRequest + +import sentry_sdk +from sentry_sdk.consts import OP + +from sentry_sdk.integrations.asgi import SentryAsgiMiddleware +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Callable, Union, TypeVar + + from django.core.handlers.asgi import ASGIRequest + from django.http.response import HttpResponse + + from sentry_sdk._types import Event, EventProcessor + + _F = TypeVar("_F", bound=Callable[..., Any]) + + +# Python 3.12 deprecates asyncio.iscoroutinefunction() as an alias for +# inspect.iscoroutinefunction(), whilst also removing the _is_coroutine marker. +# The latter is replaced with the inspect.markcoroutinefunction decorator. +# Until 3.12 is the minimum supported Python version, provide a shim. +# This was copied from https://github.com/django/asgiref/blob/main/asgiref/sync.py +if hasattr(inspect, "markcoroutinefunction"): + iscoroutinefunction = inspect.iscoroutinefunction + markcoroutinefunction = inspect.markcoroutinefunction +else: + iscoroutinefunction = asyncio.iscoroutinefunction # type: ignore[assignment] + + def markcoroutinefunction(func: "_F") -> "_F": + func._is_coroutine = asyncio.coroutines._is_coroutine # type: ignore + return func + + +def _make_asgi_request_event_processor(request): + # type: (ASGIRequest) -> EventProcessor + def asgi_request_event_processor(event, hint): + # type: (Event, dict[str, Any]) -> Event + # if the request is gone we are fine not logging the data from + # it. This might happen if the processor is pushed away to + # another thread. + from sentry_sdk.integrations.django import ( + DjangoRequestExtractor, + _set_user_info, + ) + + if request is None: + return event + + if type(request) == WSGIRequest: + return event + + with capture_internal_exceptions(): + DjangoRequestExtractor(request).extract_into_event(event) + + if should_send_default_pii(): + with capture_internal_exceptions(): + _set_user_info(request, event) + + return event + + return asgi_request_event_processor + + +def patch_django_asgi_handler_impl(cls): + # type: (Any) -> None + + from sentry_sdk.integrations.django import DjangoIntegration + + old_app = cls.__call__ + + async def sentry_patched_asgi_handler(self, scope, receive, send): + # type: (Any, Any, Any, Any) -> Any + integration = sentry_sdk.get_client().get_integration(DjangoIntegration) + if integration is None: + return await old_app(self, scope, receive, send) + + middleware = SentryAsgiMiddleware( + old_app.__get__(self, cls), + unsafe_context_data=True, + span_origin=DjangoIntegration.origin, + http_methods_to_capture=integration.http_methods_to_capture, + )._run_asgi3 + + return await middleware(scope, receive, send) + + cls.__call__ = sentry_patched_asgi_handler + + modern_django_asgi_support = hasattr(cls, "create_request") + if modern_django_asgi_support: + old_create_request = cls.create_request + + @ensure_integration_enabled(DjangoIntegration, old_create_request) + def sentry_patched_create_request(self, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + request, error_response = old_create_request(self, *args, **kwargs) + scope = sentry_sdk.get_isolation_scope() + scope.add_event_processor(_make_asgi_request_event_processor(request)) + + return request, error_response + + cls.create_request = sentry_patched_create_request + + +def patch_get_response_async(cls, _before_get_response): + # type: (Any, Any) -> None + old_get_response_async = cls.get_response_async + + async def sentry_patched_get_response_async(self, request): + # type: (Any, Any) -> Union[HttpResponse, BaseException] + _before_get_response(request) + return await old_get_response_async(self, request) + + cls.get_response_async = sentry_patched_get_response_async + + +def patch_channels_asgi_handler_impl(cls): + # type: (Any) -> None + import channels # type: ignore + + from sentry_sdk.integrations.django import DjangoIntegration + + if channels.__version__ < "3.0.0": + old_app = cls.__call__ + + async def sentry_patched_asgi_handler(self, receive, send): + # type: (Any, Any, Any) -> Any + integration = sentry_sdk.get_client().get_integration(DjangoIntegration) + if integration is None: + return await old_app(self, receive, send) + + middleware = SentryAsgiMiddleware( + lambda _scope: old_app.__get__(self, cls), + unsafe_context_data=True, + span_origin=DjangoIntegration.origin, + http_methods_to_capture=integration.http_methods_to_capture, + ) + + return await middleware(self.scope)(receive, send) + + cls.__call__ = sentry_patched_asgi_handler + + else: + # The ASGI handler in Channels >= 3 has the same signature as + # the Django handler. + patch_django_asgi_handler_impl(cls) + + +def wrap_async_view(callback): + # type: (Any) -> Any + from sentry_sdk.integrations.django import DjangoIntegration + + @functools.wraps(callback) + async def sentry_wrapped_callback(request, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + current_scope = sentry_sdk.get_current_scope() + if current_scope.transaction is not None: + current_scope.transaction.update_active_thread() + + sentry_scope = sentry_sdk.get_isolation_scope() + if sentry_scope.profile is not None: + sentry_scope.profile.update_active_thread_id() + + with sentry_sdk.start_span( + op=OP.VIEW_RENDER, + name=request.resolver_match.view_name, + origin=DjangoIntegration.origin, + ): + return await callback(request, *args, **kwargs) + + return sentry_wrapped_callback + + +def _asgi_middleware_mixin_factory(_check_middleware_span): + # type: (Callable[..., Any]) -> Any + """ + Mixin class factory that generates a middleware mixin for handling requests + in async mode. + """ + + class SentryASGIMixin: + if TYPE_CHECKING: + _inner = None + + def __init__(self, get_response): + # type: (Callable[..., Any]) -> None + self.get_response = get_response + self._acall_method = None + self._async_check() + + def _async_check(self): + # type: () -> None + """ + If get_response is a coroutine function, turns us into async mode so + a thread is not consumed during a whole request. + Taken from django.utils.deprecation::MiddlewareMixin._async_check + """ + if iscoroutinefunction(self.get_response): + markcoroutinefunction(self) + + def async_route_check(self): + # type: () -> bool + """ + Function that checks if we are in async mode, + and if we are forwards the handling of requests to __acall__ + """ + return iscoroutinefunction(self.get_response) + + async def __acall__(self, *args, **kwargs): + # type: (*Any, **Any) -> Any + f = self._acall_method + if f is None: + if hasattr(self._inner, "__acall__"): + self._acall_method = f = self._inner.__acall__ # type: ignore + else: + self._acall_method = f = self._inner + + middleware_span = _check_middleware_span(old_method=f) + + if middleware_span is None: + return await f(*args, **kwargs) + + with middleware_span: + return await f(*args, **kwargs) + + return SentryASGIMixin diff --git a/aws/lambda_demo/sentry_sdk/integrations/django/caching.py b/aws/lambda_demo/sentry_sdk/integrations/django/caching.py new file mode 100644 index 000000000..798561176 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/django/caching.py @@ -0,0 +1,191 @@ +import functools +from typing import TYPE_CHECKING +from sentry_sdk.integrations.redis.utils import _get_safe_key, _key_as_string +from urllib3.util import parse_url as urlparse + +from django import VERSION as DJANGO_VERSION +from django.core.cache import CacheHandler + +import sentry_sdk +from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, +) + + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import Optional + + +METHODS_TO_INSTRUMENT = [ + "set", + "set_many", + "get", + "get_many", +] + + +def _get_span_description(method_name, args, kwargs): + # type: (str, tuple[Any], dict[str, Any]) -> str + return _key_as_string(_get_safe_key(method_name, args, kwargs)) + + +def _patch_cache_method(cache, method_name, address, port): + # type: (CacheHandler, str, Optional[str], Optional[int]) -> None + from sentry_sdk.integrations.django import DjangoIntegration + + original_method = getattr(cache, method_name) + + @ensure_integration_enabled(DjangoIntegration, original_method) + def _instrument_call( + cache, method_name, original_method, args, kwargs, address, port + ): + # type: (CacheHandler, str, Callable[..., Any], tuple[Any, ...], dict[str, Any], Optional[str], Optional[int]) -> Any + is_set_operation = method_name.startswith("set") + is_get_operation = not is_set_operation + + op = OP.CACHE_PUT if is_set_operation else OP.CACHE_GET + description = _get_span_description(method_name, args, kwargs) + + with sentry_sdk.start_span( + op=op, + name=description, + origin=DjangoIntegration.origin, + ) as span: + value = original_method(*args, **kwargs) + + with capture_internal_exceptions(): + if address is not None: + span.set_data(SPANDATA.NETWORK_PEER_ADDRESS, address) + + if port is not None: + span.set_data(SPANDATA.NETWORK_PEER_PORT, port) + + key = _get_safe_key(method_name, args, kwargs) + if key is not None: + span.set_data(SPANDATA.CACHE_KEY, key) + + item_size = None + if is_get_operation: + if value: + item_size = len(str(value)) + span.set_data(SPANDATA.CACHE_HIT, True) + else: + span.set_data(SPANDATA.CACHE_HIT, False) + else: # TODO: We don't handle `get_or_set` which we should + arg_count = len(args) + if arg_count >= 2: + # 'set' command + item_size = len(str(args[1])) + elif arg_count == 1: + # 'set_many' command + item_size = len(str(args[0])) + + if item_size is not None: + span.set_data(SPANDATA.CACHE_ITEM_SIZE, item_size) + + return value + + @functools.wraps(original_method) + def sentry_method(*args, **kwargs): + # type: (*Any, **Any) -> Any + return _instrument_call( + cache, method_name, original_method, args, kwargs, address, port + ) + + setattr(cache, method_name, sentry_method) + + +def _patch_cache(cache, address=None, port=None): + # type: (CacheHandler, Optional[str], Optional[int]) -> None + if not hasattr(cache, "_sentry_patched"): + for method_name in METHODS_TO_INSTRUMENT: + _patch_cache_method(cache, method_name, address, port) + cache._sentry_patched = True + + +def _get_address_port(settings): + # type: (dict[str, Any]) -> tuple[Optional[str], Optional[int]] + location = settings.get("LOCATION") + + # TODO: location can also be an array of locations + # see: https://docs.djangoproject.com/en/5.0/topics/cache/#redis + # GitHub issue: https://github.com/getsentry/sentry-python/issues/3062 + if not isinstance(location, str): + return None, None + + if "://" in location: + parsed_url = urlparse(location) + # remove the username and password from URL to not leak sensitive data. + address = "{}://{}{}".format( + parsed_url.scheme or "", + parsed_url.hostname or "", + parsed_url.path or "", + ) + port = parsed_url.port + else: + address = location + port = None + + return address, int(port) if port is not None else None + + +def should_enable_cache_spans(): + # type: () -> bool + from sentry_sdk.integrations.django import DjangoIntegration + + client = sentry_sdk.get_client() + integration = client.get_integration(DjangoIntegration) + from django.conf import settings + + return integration is not None and ( + (client.spotlight is not None and settings.DEBUG is True) + or integration.cache_spans is True + ) + + +def patch_caching(): + # type: () -> None + if not hasattr(CacheHandler, "_sentry_patched"): + if DJANGO_VERSION < (3, 2): + original_get_item = CacheHandler.__getitem__ + + @functools.wraps(original_get_item) + def sentry_get_item(self, alias): + # type: (CacheHandler, str) -> Any + cache = original_get_item(self, alias) + + if should_enable_cache_spans(): + from django.conf import settings + + address, port = _get_address_port( + settings.CACHES[alias or "default"] + ) + + _patch_cache(cache, address, port) + + return cache + + CacheHandler.__getitem__ = sentry_get_item + CacheHandler._sentry_patched = True + + else: + original_create_connection = CacheHandler.create_connection + + @functools.wraps(original_create_connection) + def sentry_create_connection(self, alias): + # type: (CacheHandler, str) -> Any + cache = original_create_connection(self, alias) + + if should_enable_cache_spans(): + address, port = _get_address_port(self.settings[alias or "default"]) + + _patch_cache(cache, address, port) + + return cache + + CacheHandler.create_connection = sentry_create_connection + CacheHandler._sentry_patched = True diff --git a/aws/lambda_demo/sentry_sdk/integrations/django/middleware.py b/aws/lambda_demo/sentry_sdk/integrations/django/middleware.py new file mode 100644 index 000000000..245276566 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/django/middleware.py @@ -0,0 +1,187 @@ +""" +Create spans from Django middleware invocations +""" + +from functools import wraps + +from django import VERSION as DJANGO_VERSION + +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.utils import ( + ContextVar, + transaction_from_function, + capture_internal_exceptions, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import Optional + from typing import TypeVar + + from sentry_sdk.tracing import Span + + F = TypeVar("F", bound=Callable[..., Any]) + +_import_string_should_wrap_middleware = ContextVar( + "import_string_should_wrap_middleware" +) + +DJANGO_SUPPORTS_ASYNC_MIDDLEWARE = DJANGO_VERSION >= (3, 1) + +if not DJANGO_SUPPORTS_ASYNC_MIDDLEWARE: + _asgi_middleware_mixin_factory = lambda _: object +else: + from .asgi import _asgi_middleware_mixin_factory + + +def patch_django_middlewares(): + # type: () -> None + from django.core.handlers import base + + old_import_string = base.import_string + + def sentry_patched_import_string(dotted_path): + # type: (str) -> Any + rv = old_import_string(dotted_path) + + if _import_string_should_wrap_middleware.get(None): + rv = _wrap_middleware(rv, dotted_path) + + return rv + + base.import_string = sentry_patched_import_string + + old_load_middleware = base.BaseHandler.load_middleware + + def sentry_patched_load_middleware(*args, **kwargs): + # type: (Any, Any) -> Any + _import_string_should_wrap_middleware.set(True) + try: + return old_load_middleware(*args, **kwargs) + finally: + _import_string_should_wrap_middleware.set(False) + + base.BaseHandler.load_middleware = sentry_patched_load_middleware + + +def _wrap_middleware(middleware, middleware_name): + # type: (Any, str) -> Any + from sentry_sdk.integrations.django import DjangoIntegration + + def _check_middleware_span(old_method): + # type: (Callable[..., Any]) -> Optional[Span] + integration = sentry_sdk.get_client().get_integration(DjangoIntegration) + if integration is None or not integration.middleware_spans: + return None + + function_name = transaction_from_function(old_method) + + description = middleware_name + function_basename = getattr(old_method, "__name__", None) + if function_basename: + description = "{}.{}".format(description, function_basename) + + middleware_span = sentry_sdk.start_span( + op=OP.MIDDLEWARE_DJANGO, + name=description, + origin=DjangoIntegration.origin, + ) + middleware_span.set_tag("django.function_name", function_name) + middleware_span.set_tag("django.middleware_name", middleware_name) + + return middleware_span + + def _get_wrapped_method(old_method): + # type: (F) -> F + with capture_internal_exceptions(): + + def sentry_wrapped_method(*args, **kwargs): + # type: (*Any, **Any) -> Any + middleware_span = _check_middleware_span(old_method) + + if middleware_span is None: + return old_method(*args, **kwargs) + + with middleware_span: + return old_method(*args, **kwargs) + + try: + # fails for __call__ of function on Python 2 (see py2.7-django-1.11) + sentry_wrapped_method = wraps(old_method)(sentry_wrapped_method) + + # Necessary for Django 3.1 + sentry_wrapped_method.__self__ = old_method.__self__ # type: ignore + except Exception: + pass + + return sentry_wrapped_method # type: ignore + + return old_method + + class SentryWrappingMiddleware( + _asgi_middleware_mixin_factory(_check_middleware_span) # type: ignore + ): + sync_capable = getattr(middleware, "sync_capable", True) + async_capable = DJANGO_SUPPORTS_ASYNC_MIDDLEWARE and getattr( + middleware, "async_capable", False + ) + + def __init__(self, get_response=None, *args, **kwargs): + # type: (Optional[Callable[..., Any]], *Any, **Any) -> None + if get_response: + self._inner = middleware(get_response, *args, **kwargs) + else: + self._inner = middleware(*args, **kwargs) + self.get_response = get_response + self._call_method = None + if self.async_capable: + super().__init__(get_response) + + # We need correct behavior for `hasattr()`, which we can only determine + # when we have an instance of the middleware we're wrapping. + def __getattr__(self, method_name): + # type: (str) -> Any + if method_name not in ( + "process_request", + "process_view", + "process_template_response", + "process_response", + "process_exception", + ): + raise AttributeError() + + old_method = getattr(self._inner, method_name) + rv = _get_wrapped_method(old_method) + self.__dict__[method_name] = rv + return rv + + def __call__(self, *args, **kwargs): + # type: (*Any, **Any) -> Any + if hasattr(self, "async_route_check") and self.async_route_check(): + return self.__acall__(*args, **kwargs) + + f = self._call_method + if f is None: + self._call_method = f = self._inner.__call__ + + middleware_span = _check_middleware_span(old_method=f) + + if middleware_span is None: + return f(*args, **kwargs) + + with middleware_span: + return f(*args, **kwargs) + + for attr in ( + "__name__", + "__module__", + "__qualname__", + ): + if hasattr(middleware, attr): + setattr(SentryWrappingMiddleware, attr, getattr(middleware, attr)) + + return SentryWrappingMiddleware diff --git a/aws/lambda_demo/sentry_sdk/integrations/django/signals_handlers.py b/aws/lambda_demo/sentry_sdk/integrations/django/signals_handlers.py new file mode 100644 index 000000000..cb0f8b9d2 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/django/signals_handlers.py @@ -0,0 +1,91 @@ +from functools import wraps + +from django.dispatch import Signal + +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.integrations.django import DJANGO_VERSION + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Callable + from typing import Any, Union + + +def _get_receiver_name(receiver): + # type: (Callable[..., Any]) -> str + name = "" + + if hasattr(receiver, "__qualname__"): + name = receiver.__qualname__ + elif hasattr(receiver, "__name__"): # Python 2.7 has no __qualname__ + name = receiver.__name__ + elif hasattr( + receiver, "func" + ): # certain functions (like partials) dont have a name + if hasattr(receiver, "func") and hasattr(receiver.func, "__name__"): + name = "partial()" + + if ( + name == "" + ): # In case nothing was found, return the string representation (this is the slowest case) + return str(receiver) + + if hasattr(receiver, "__module__"): # prepend with module, if there is one + name = receiver.__module__ + "." + name + + return name + + +def patch_signals(): + # type: () -> None + """ + Patch django signal receivers to create a span. + + This only wraps sync receivers. Django>=5.0 introduced async receivers, but + since we don't create transactions for ASGI Django, we don't wrap them. + """ + from sentry_sdk.integrations.django import DjangoIntegration + + old_live_receivers = Signal._live_receivers + + def _sentry_live_receivers(self, sender): + # type: (Signal, Any) -> Union[tuple[list[Callable[..., Any]], list[Callable[..., Any]]], list[Callable[..., Any]]] + if DJANGO_VERSION >= (5, 0): + sync_receivers, async_receivers = old_live_receivers(self, sender) + else: + sync_receivers = old_live_receivers(self, sender) + async_receivers = [] + + def sentry_sync_receiver_wrapper(receiver): + # type: (Callable[..., Any]) -> Callable[..., Any] + @wraps(receiver) + def wrapper(*args, **kwargs): + # type: (Any, Any) -> Any + signal_name = _get_receiver_name(receiver) + with sentry_sdk.start_span( + op=OP.EVENT_DJANGO, + name=signal_name, + origin=DjangoIntegration.origin, + ) as span: + span.set_data("signal", signal_name) + return receiver(*args, **kwargs) + + return wrapper + + integration = sentry_sdk.get_client().get_integration(DjangoIntegration) + if ( + integration + and integration.signals_spans + and self not in integration.signals_denylist + ): + for idx, receiver in enumerate(sync_receivers): + sync_receivers[idx] = sentry_sync_receiver_wrapper(receiver) + + if DJANGO_VERSION >= (5, 0): + return sync_receivers, async_receivers + else: + return sync_receivers + + Signal._live_receivers = _sentry_live_receivers diff --git a/aws/lambda_demo/sentry_sdk/integrations/django/templates.py b/aws/lambda_demo/sentry_sdk/integrations/django/templates.py new file mode 100644 index 000000000..10e8a924b --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/django/templates.py @@ -0,0 +1,188 @@ +import functools + +from django.template import TemplateSyntaxError +from django.utils.safestring import mark_safe +from django import VERSION as DJANGO_VERSION + +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.utils import ensure_integration_enabled + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Dict + from typing import Optional + from typing import Iterator + from typing import Tuple + +try: + # support Django 1.9 + from django.template.base import Origin +except ImportError: + # backward compatibility + from django.template.loader import LoaderOrigin as Origin + + +def get_template_frame_from_exception(exc_value): + # type: (Optional[BaseException]) -> Optional[Dict[str, Any]] + + # As of Django 1.9 or so the new template debug thing showed up. + if hasattr(exc_value, "template_debug"): + return _get_template_frame_from_debug(exc_value.template_debug) # type: ignore + + # As of r16833 (Django) all exceptions may contain a + # ``django_template_source`` attribute (rather than the legacy + # ``TemplateSyntaxError.source`` check) + if hasattr(exc_value, "django_template_source"): + return _get_template_frame_from_source( + exc_value.django_template_source # type: ignore + ) + + if isinstance(exc_value, TemplateSyntaxError) and hasattr(exc_value, "source"): + source = exc_value.source + if isinstance(source, (tuple, list)) and isinstance(source[0], Origin): + return _get_template_frame_from_source(source) # type: ignore + + return None + + +def _get_template_name_description(template_name): + # type: (str) -> str + if isinstance(template_name, (list, tuple)): + if template_name: + return "[{}, ...]".format(template_name[0]) + else: + return template_name + + +def patch_templates(): + # type: () -> None + from django.template.response import SimpleTemplateResponse + from sentry_sdk.integrations.django import DjangoIntegration + + real_rendered_content = SimpleTemplateResponse.rendered_content + + @property # type: ignore + @ensure_integration_enabled(DjangoIntegration, real_rendered_content.fget) + def rendered_content(self): + # type: (SimpleTemplateResponse) -> str + with sentry_sdk.start_span( + op=OP.TEMPLATE_RENDER, + name=_get_template_name_description(self.template_name), + origin=DjangoIntegration.origin, + ) as span: + span.set_data("context", self.context_data) + return real_rendered_content.fget(self) + + SimpleTemplateResponse.rendered_content = rendered_content + + if DJANGO_VERSION < (1, 7): + return + import django.shortcuts + + real_render = django.shortcuts.render + + @functools.wraps(real_render) + @ensure_integration_enabled(DjangoIntegration, real_render) + def render(request, template_name, context=None, *args, **kwargs): + # type: (django.http.HttpRequest, str, Optional[Dict[str, Any]], *Any, **Any) -> django.http.HttpResponse + + # Inject trace meta tags into template context + context = context or {} + if "sentry_trace_meta" not in context: + context["sentry_trace_meta"] = mark_safe( + sentry_sdk.get_current_scope().trace_propagation_meta() + ) + + with sentry_sdk.start_span( + op=OP.TEMPLATE_RENDER, + name=_get_template_name_description(template_name), + origin=DjangoIntegration.origin, + ) as span: + span.set_data("context", context) + return real_render(request, template_name, context, *args, **kwargs) + + django.shortcuts.render = render + + +def _get_template_frame_from_debug(debug): + # type: (Dict[str, Any]) -> Dict[str, Any] + if debug is None: + return None + + lineno = debug["line"] + filename = debug["name"] + if filename is None: + filename = "" + + pre_context = [] + post_context = [] + context_line = None + + for i, line in debug["source_lines"]: + if i < lineno: + pre_context.append(line) + elif i > lineno: + post_context.append(line) + else: + context_line = line + + return { + "filename": filename, + "lineno": lineno, + "pre_context": pre_context[-5:], + "post_context": post_context[:5], + "context_line": context_line, + "in_app": True, + } + + +def _linebreak_iter(template_source): + # type: (str) -> Iterator[int] + yield 0 + p = template_source.find("\n") + while p >= 0: + yield p + 1 + p = template_source.find("\n", p + 1) + + +def _get_template_frame_from_source(source): + # type: (Tuple[Origin, Tuple[int, int]]) -> Optional[Dict[str, Any]] + if not source: + return None + + origin, (start, end) = source + filename = getattr(origin, "loadname", None) + if filename is None: + filename = "" + template_source = origin.reload() + lineno = None + upto = 0 + pre_context = [] + post_context = [] + context_line = None + + for num, next in enumerate(_linebreak_iter(template_source)): + line = template_source[upto:next] + if start >= upto and end <= next: + lineno = num + context_line = line + elif lineno is None: + pre_context.append(line) + else: + post_context.append(line) + + upto = next + + if context_line is None or lineno is None: + return None + + return { + "filename": filename, + "lineno": lineno, + "pre_context": pre_context[-5:], + "post_context": post_context[:5], + "context_line": context_line, + } diff --git a/aws/lambda_demo/sentry_sdk/integrations/django/transactions.py b/aws/lambda_demo/sentry_sdk/integrations/django/transactions.py new file mode 100644 index 000000000..5a7d69f3c --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/django/transactions.py @@ -0,0 +1,159 @@ +""" +Copied from raven-python. + +Despite being called "legacy" in some places this resolver is very much still +in use. +""" + +import re + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from django.urls.resolvers import URLResolver + from typing import Dict + from typing import List + from typing import Optional + from django.urls.resolvers import URLPattern + from typing import Tuple + from typing import Union + from re import Pattern + +from django import VERSION as DJANGO_VERSION + +if DJANGO_VERSION >= (2, 0): + from django.urls.resolvers import RoutePattern +else: + RoutePattern = None + +try: + from django.urls import get_resolver +except ImportError: + from django.core.urlresolvers import get_resolver + + +def get_regex(resolver_or_pattern): + # type: (Union[URLPattern, URLResolver]) -> Pattern[str] + """Utility method for django's deprecated resolver.regex""" + try: + regex = resolver_or_pattern.regex + except AttributeError: + regex = resolver_or_pattern.pattern.regex + return regex + + +class RavenResolver: + _new_style_group_matcher = re.compile( + r"<(?:([^>:]+):)?([^>]+)>" + ) # https://github.com/django/django/blob/21382e2743d06efbf5623e7c9b6dccf2a325669b/django/urls/resolvers.py#L245-L247 + _optional_group_matcher = re.compile(r"\(\?\:([^\)]+)\)") + _named_group_matcher = re.compile(r"\(\?P<(\w+)>[^\)]+\)+") + _non_named_group_matcher = re.compile(r"\([^\)]+\)") + # [foo|bar|baz] + _either_option_matcher = re.compile(r"\[([^\]]+)\|([^\]]+)\]") + _camel_re = re.compile(r"([A-Z]+)([a-z])") + + _cache = {} # type: Dict[URLPattern, str] + + def _simplify(self, pattern): + # type: (Union[URLPattern, URLResolver]) -> str + r""" + Clean up urlpattern regexes into something readable by humans: + + From: + > "^(?P\w+)/athletes/(?P\w+)/$" + + To: + > "{sport_slug}/athletes/{athlete_slug}/" + """ + # "new-style" path patterns can be parsed directly without turning them + # into regexes first + if ( + RoutePattern is not None + and hasattr(pattern, "pattern") + and isinstance(pattern.pattern, RoutePattern) + ): + return self._new_style_group_matcher.sub( + lambda m: "{%s}" % m.group(2), str(pattern.pattern._route) + ) + + result = get_regex(pattern).pattern + + # remove optional params + # TODO(dcramer): it'd be nice to change these into [%s] but it currently + # conflicts with the other rules because we're doing regexp matches + # rather than parsing tokens + result = self._optional_group_matcher.sub(lambda m: "%s" % m.group(1), result) + + # handle named groups first + result = self._named_group_matcher.sub(lambda m: "{%s}" % m.group(1), result) + + # handle non-named groups + result = self._non_named_group_matcher.sub("{var}", result) + + # handle optional params + result = self._either_option_matcher.sub(lambda m: m.group(1), result) + + # clean up any outstanding regex-y characters. + result = ( + result.replace("^", "") + .replace("$", "") + .replace("?", "") + .replace("\\A", "") + .replace("\\Z", "") + .replace("//", "/") + .replace("\\", "") + ) + + return result + + def _resolve(self, resolver, path, parents=None): + # type: (URLResolver, str, Optional[List[URLResolver]]) -> Optional[str] + + match = get_regex(resolver).search(path) # Django < 2.0 + + if not match: + return None + + if parents is None: + parents = [resolver] + elif resolver not in parents: + parents = parents + [resolver] + + new_path = path[match.end() :] + for pattern in resolver.url_patterns: + # this is an include() + if not pattern.callback: + match_ = self._resolve(pattern, new_path, parents) + if match_: + return match_ + continue + elif not get_regex(pattern).search(new_path): + continue + + try: + return self._cache[pattern] + except KeyError: + pass + + prefix = "".join(self._simplify(p) for p in parents) + result = prefix + self._simplify(pattern) + if not result.startswith("/"): + result = "/" + result + self._cache[pattern] = result + return result + + return None + + def resolve( + self, + path, # type: str + urlconf=None, # type: Union[None, Tuple[URLPattern, URLPattern, URLResolver], Tuple[URLPattern]] + ): + # type: (...) -> Optional[str] + resolver = get_resolver(urlconf) + match = self._resolve(resolver, path) + return match + + +LEGACY_RESOLVER = RavenResolver() diff --git a/aws/lambda_demo/sentry_sdk/integrations/django/views.py b/aws/lambda_demo/sentry_sdk/integrations/django/views.py new file mode 100644 index 000000000..0a9861a6a --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/django/views.py @@ -0,0 +1,96 @@ +import functools + +import sentry_sdk +from sentry_sdk.consts import OP + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + + +try: + from asyncio import iscoroutinefunction +except ImportError: + iscoroutinefunction = None # type: ignore + + +try: + from sentry_sdk.integrations.django.asgi import wrap_async_view +except (ImportError, SyntaxError): + wrap_async_view = None # type: ignore + + +def patch_views(): + # type: () -> None + + from django.core.handlers.base import BaseHandler + from django.template.response import SimpleTemplateResponse + from sentry_sdk.integrations.django import DjangoIntegration + + old_make_view_atomic = BaseHandler.make_view_atomic + old_render = SimpleTemplateResponse.render + + def sentry_patched_render(self): + # type: (SimpleTemplateResponse) -> Any + with sentry_sdk.start_span( + op=OP.VIEW_RESPONSE_RENDER, + name="serialize response", + origin=DjangoIntegration.origin, + ): + return old_render(self) + + @functools.wraps(old_make_view_atomic) + def sentry_patched_make_view_atomic(self, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + callback = old_make_view_atomic(self, *args, **kwargs) + + # XXX: The wrapper function is created for every request. Find more + # efficient way to wrap views (or build a cache?) + + integration = sentry_sdk.get_client().get_integration(DjangoIntegration) + if integration is not None and integration.middleware_spans: + is_async_view = ( + iscoroutinefunction is not None + and wrap_async_view is not None + and iscoroutinefunction(callback) + ) + if is_async_view: + sentry_wrapped_callback = wrap_async_view(callback) + else: + sentry_wrapped_callback = _wrap_sync_view(callback) + + else: + sentry_wrapped_callback = callback + + return sentry_wrapped_callback + + SimpleTemplateResponse.render = sentry_patched_render + BaseHandler.make_view_atomic = sentry_patched_make_view_atomic + + +def _wrap_sync_view(callback): + # type: (Any) -> Any + from sentry_sdk.integrations.django import DjangoIntegration + + @functools.wraps(callback) + def sentry_wrapped_callback(request, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + current_scope = sentry_sdk.get_current_scope() + if current_scope.transaction is not None: + current_scope.transaction.update_active_thread() + + sentry_scope = sentry_sdk.get_isolation_scope() + # set the active thread id to the handler thread for sync views + # this isn't necessary for async views since that runs on main + if sentry_scope.profile is not None: + sentry_scope.profile.update_active_thread_id() + + with sentry_sdk.start_span( + op=OP.VIEW_RENDER, + name=request.resolver_match.view_name, + origin=DjangoIntegration.origin, + ): + return callback(request, *args, **kwargs) + + return sentry_wrapped_callback diff --git a/aws/lambda_demo/sentry_sdk/integrations/dramatiq.py b/aws/lambda_demo/sentry_sdk/integrations/dramatiq.py new file mode 100644 index 000000000..f9ef13e20 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/dramatiq.py @@ -0,0 +1,168 @@ +import json + +import sentry_sdk +from sentry_sdk.integrations import Integration +from sentry_sdk.integrations._wsgi_common import request_body_within_bounds +from sentry_sdk.utils import ( + AnnotatedValue, + capture_internal_exceptions, + event_from_exception, +) + +from dramatiq.broker import Broker # type: ignore +from dramatiq.message import Message # type: ignore +from dramatiq.middleware import Middleware, default_middleware # type: ignore +from dramatiq.errors import Retry # type: ignore + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Callable, Dict, Optional, Union + from sentry_sdk._types import Event, Hint + + +class DramatiqIntegration(Integration): + """ + Dramatiq integration for Sentry + + Please make sure that you call `sentry_sdk.init` *before* initializing + your broker, as it monkey patches `Broker.__init__`. + + This integration was originally developed and maintained + by https://github.com/jacobsvante and later donated to the Sentry + project. + """ + + identifier = "dramatiq" + + @staticmethod + def setup_once(): + # type: () -> None + _patch_dramatiq_broker() + + +def _patch_dramatiq_broker(): + # type: () -> None + original_broker__init__ = Broker.__init__ + + def sentry_patched_broker__init__(self, *args, **kw): + # type: (Broker, *Any, **Any) -> None + integration = sentry_sdk.get_client().get_integration(DramatiqIntegration) + + try: + middleware = kw.pop("middleware") + except KeyError: + # Unfortunately Broker and StubBroker allows middleware to be + # passed in as positional arguments, whilst RabbitmqBroker and + # RedisBroker does not. + if len(args) == 1: + middleware = args[0] + args = [] # type: ignore + else: + middleware = None + + if middleware is None: + middleware = list(m() for m in default_middleware) + else: + middleware = list(middleware) + + if integration is not None: + middleware = [m for m in middleware if not isinstance(m, SentryMiddleware)] + middleware.insert(0, SentryMiddleware()) + + kw["middleware"] = middleware + original_broker__init__(self, *args, **kw) + + Broker.__init__ = sentry_patched_broker__init__ + + +class SentryMiddleware(Middleware): # type: ignore[misc] + """ + A Dramatiq middleware that automatically captures and sends + exceptions to Sentry. + + This is automatically added to every instantiated broker via the + DramatiqIntegration. + """ + + def before_process_message(self, broker, message): + # type: (Broker, Message) -> None + integration = sentry_sdk.get_client().get_integration(DramatiqIntegration) + if integration is None: + return + + message._scope_manager = sentry_sdk.new_scope() + message._scope_manager.__enter__() + + scope = sentry_sdk.get_current_scope() + scope.transaction = message.actor_name + scope.set_extra("dramatiq_message_id", message.message_id) + scope.add_event_processor(_make_message_event_processor(message, integration)) + + def after_process_message(self, broker, message, *, result=None, exception=None): + # type: (Broker, Message, Any, Optional[Any], Optional[Exception]) -> None + integration = sentry_sdk.get_client().get_integration(DramatiqIntegration) + if integration is None: + return + + actor = broker.get_actor(message.actor_name) + throws = message.options.get("throws") or actor.options.get("throws") + + try: + if ( + exception is not None + and not (throws and isinstance(exception, throws)) + and not isinstance(exception, Retry) + ): + event, hint = event_from_exception( + exception, + client_options=sentry_sdk.get_client().options, + mechanism={ + "type": DramatiqIntegration.identifier, + "handled": False, + }, + ) + sentry_sdk.capture_event(event, hint=hint) + finally: + message._scope_manager.__exit__(None, None, None) + + +def _make_message_event_processor(message, integration): + # type: (Message, DramatiqIntegration) -> Callable[[Event, Hint], Optional[Event]] + + def inner(event, hint): + # type: (Event, Hint) -> Optional[Event] + with capture_internal_exceptions(): + DramatiqMessageExtractor(message).extract_into_event(event) + + return event + + return inner + + +class DramatiqMessageExtractor: + def __init__(self, message): + # type: (Message) -> None + self.message_data = dict(message.asdict()) + + def content_length(self): + # type: () -> int + return len(json.dumps(self.message_data)) + + def extract_into_event(self, event): + # type: (Event) -> None + client = sentry_sdk.get_client() + if not client.is_active(): + return + + contexts = event.setdefault("contexts", {}) + request_info = contexts.setdefault("dramatiq", {}) + request_info["type"] = "dramatiq" + + data = None # type: Optional[Union[AnnotatedValue, Dict[str, Any]]] + if not request_body_within_bounds(client, self.content_length()): + data = AnnotatedValue.removed_because_over_size_limit() + else: + data = self.message_data + + request_info["data"] = data diff --git a/aws/lambda_demo/sentry_sdk/integrations/excepthook.py b/aws/lambda_demo/sentry_sdk/integrations/excepthook.py new file mode 100644 index 000000000..61c7e460b --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/excepthook.py @@ -0,0 +1,83 @@ +import sys + +import sentry_sdk +from sentry_sdk.utils import ( + capture_internal_exceptions, + event_from_exception, +) +from sentry_sdk.integrations import Integration + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Callable + from typing import Any + from typing import Type + from typing import Optional + + from types import TracebackType + + Excepthook = Callable[ + [Type[BaseException], BaseException, Optional[TracebackType]], + Any, + ] + + +class ExcepthookIntegration(Integration): + identifier = "excepthook" + + always_run = False + + def __init__(self, always_run=False): + # type: (bool) -> None + + if not isinstance(always_run, bool): + raise ValueError( + "Invalid value for always_run: %s (must be type boolean)" + % (always_run,) + ) + self.always_run = always_run + + @staticmethod + def setup_once(): + # type: () -> None + sys.excepthook = _make_excepthook(sys.excepthook) + + +def _make_excepthook(old_excepthook): + # type: (Excepthook) -> Excepthook + def sentry_sdk_excepthook(type_, value, traceback): + # type: (Type[BaseException], BaseException, Optional[TracebackType]) -> None + integration = sentry_sdk.get_client().get_integration(ExcepthookIntegration) + + # Note: If we replace this with ensure_integration_enabled then + # we break the exceptiongroup backport; + # See: https://github.com/getsentry/sentry-python/issues/3097 + if integration is None: + return old_excepthook(type_, value, traceback) + + if _should_send(integration.always_run): + with capture_internal_exceptions(): + event, hint = event_from_exception( + (type_, value, traceback), + client_options=sentry_sdk.get_client().options, + mechanism={"type": "excepthook", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + return old_excepthook(type_, value, traceback) + + return sentry_sdk_excepthook + + +def _should_send(always_run=False): + # type: (bool) -> bool + if always_run: + return True + + if hasattr(sys, "ps1"): + # Disable the excepthook for interactive Python shells, otherwise + # every typo gets sent to Sentry. + return False + + return True diff --git a/aws/lambda_demo/sentry_sdk/integrations/executing.py b/aws/lambda_demo/sentry_sdk/integrations/executing.py new file mode 100644 index 000000000..6e68b8c0c --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/executing.py @@ -0,0 +1,67 @@ +import sentry_sdk +from sentry_sdk.integrations import Integration, DidNotEnable +from sentry_sdk.scope import add_global_event_processor +from sentry_sdk.utils import walk_exception_chain, iter_stacks + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Optional + + from sentry_sdk._types import Event, Hint + +try: + import executing +except ImportError: + raise DidNotEnable("executing is not installed") + + +class ExecutingIntegration(Integration): + identifier = "executing" + + @staticmethod + def setup_once(): + # type: () -> None + + @add_global_event_processor + def add_executing_info(event, hint): + # type: (Event, Optional[Hint]) -> Optional[Event] + if sentry_sdk.get_client().get_integration(ExecutingIntegration) is None: + return event + + if hint is None: + return event + + exc_info = hint.get("exc_info", None) + + if exc_info is None: + return event + + exception = event.get("exception", None) + + if exception is None: + return event + + values = exception.get("values", None) + + if values is None: + return event + + for exception, (_exc_type, _exc_value, exc_tb) in zip( + reversed(values), walk_exception_chain(exc_info) + ): + sentry_frames = [ + frame + for frame in exception.get("stacktrace", {}).get("frames", []) + if frame.get("function") + ] + tbs = list(iter_stacks(exc_tb)) + if len(sentry_frames) != len(tbs): + continue + + for sentry_frame, tb in zip(sentry_frames, tbs): + frame = tb.tb_frame + source = executing.Source.for_frame(frame) + sentry_frame["function"] = source.code_qualname(frame.f_code) + + return event diff --git a/aws/lambda_demo/sentry_sdk/integrations/falcon.py b/aws/lambda_demo/sentry_sdk/integrations/falcon.py new file mode 100644 index 000000000..ddedcb10d --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/falcon.py @@ -0,0 +1,272 @@ +import sentry_sdk +from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable +from sentry_sdk.integrations._wsgi_common import RequestExtractor +from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware +from sentry_sdk.tracing import SOURCE_FOR_STYLE +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + parse_version, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Dict + from typing import Optional + + from sentry_sdk._types import Event, EventProcessor + +# In Falcon 3.0 `falcon.api_helpers` is renamed to `falcon.app_helpers` +# and `falcon.API` to `falcon.App` + +try: + import falcon # type: ignore + + from falcon import __version__ as FALCON_VERSION +except ImportError: + raise DidNotEnable("Falcon not installed") + +try: + import falcon.app_helpers # type: ignore + + falcon_helpers = falcon.app_helpers + falcon_app_class = falcon.App + FALCON3 = True +except ImportError: + import falcon.api_helpers # type: ignore + + falcon_helpers = falcon.api_helpers + falcon_app_class = falcon.API + FALCON3 = False + + +_FALCON_UNSET = None # type: Optional[object] +if FALCON3: # falcon.request._UNSET is only available in Falcon 3.0+ + with capture_internal_exceptions(): + from falcon.request import _UNSET as _FALCON_UNSET # type: ignore[import-not-found, no-redef] + + +class FalconRequestExtractor(RequestExtractor): + def env(self): + # type: () -> Dict[str, Any] + return self.request.env + + def cookies(self): + # type: () -> Dict[str, Any] + return self.request.cookies + + def form(self): + # type: () -> None + return None # No such concept in Falcon + + def files(self): + # type: () -> None + return None # No such concept in Falcon + + def raw_data(self): + # type: () -> Optional[str] + + # As request data can only be read once we won't make this available + # to Sentry. Just send back a dummy string in case there was a + # content length. + # TODO(jmagnusson): Figure out if there's a way to support this + content_length = self.content_length() + if content_length > 0: + return "[REQUEST_CONTAINING_RAW_DATA]" + else: + return None + + def json(self): + # type: () -> Optional[Dict[str, Any]] + # fallback to cached_media = None if self.request._media is not available + cached_media = None + with capture_internal_exceptions(): + # self.request._media is the cached self.request.media + # value. It is only available if self.request.media + # has already been accessed. Therefore, reading + # self.request._media will not exhaust the raw request + # stream (self.request.bounded_stream) because it has + # already been read if self.request._media is set. + cached_media = self.request._media + + if cached_media is not _FALCON_UNSET: + return cached_media + + return None + + +class SentryFalconMiddleware: + """Captures exceptions in Falcon requests and send to Sentry""" + + def process_request(self, req, resp, *args, **kwargs): + # type: (Any, Any, *Any, **Any) -> None + integration = sentry_sdk.get_client().get_integration(FalconIntegration) + if integration is None: + return + + scope = sentry_sdk.get_isolation_scope() + scope._name = "falcon" + scope.add_event_processor(_make_request_event_processor(req, integration)) + + +TRANSACTION_STYLE_VALUES = ("uri_template", "path") + + +class FalconIntegration(Integration): + identifier = "falcon" + origin = f"auto.http.{identifier}" + + transaction_style = "" + + def __init__(self, transaction_style="uri_template"): + # type: (str) -> None + if transaction_style not in TRANSACTION_STYLE_VALUES: + raise ValueError( + "Invalid value for transaction_style: %s (must be in %s)" + % (transaction_style, TRANSACTION_STYLE_VALUES) + ) + self.transaction_style = transaction_style + + @staticmethod + def setup_once(): + # type: () -> None + + version = parse_version(FALCON_VERSION) + _check_minimum_version(FalconIntegration, version) + + _patch_wsgi_app() + _patch_handle_exception() + _patch_prepare_middleware() + + +def _patch_wsgi_app(): + # type: () -> None + original_wsgi_app = falcon_app_class.__call__ + + def sentry_patched_wsgi_app(self, env, start_response): + # type: (falcon.API, Any, Any) -> Any + integration = sentry_sdk.get_client().get_integration(FalconIntegration) + if integration is None: + return original_wsgi_app(self, env, start_response) + + sentry_wrapped = SentryWsgiMiddleware( + lambda envi, start_resp: original_wsgi_app(self, envi, start_resp), + span_origin=FalconIntegration.origin, + ) + + return sentry_wrapped(env, start_response) + + falcon_app_class.__call__ = sentry_patched_wsgi_app + + +def _patch_handle_exception(): + # type: () -> None + original_handle_exception = falcon_app_class._handle_exception + + @ensure_integration_enabled(FalconIntegration, original_handle_exception) + def sentry_patched_handle_exception(self, *args): + # type: (falcon.API, *Any) -> Any + # NOTE(jmagnusson): falcon 2.0 changed falcon.API._handle_exception + # method signature from `(ex, req, resp, params)` to + # `(req, resp, ex, params)` + ex = response = None + with capture_internal_exceptions(): + ex = next(argument for argument in args if isinstance(argument, Exception)) + response = next( + argument for argument in args if isinstance(argument, falcon.Response) + ) + + was_handled = original_handle_exception(self, *args) + + if ex is None or response is None: + # Both ex and response should have a non-None value at this point; otherwise, + # there is an error with the SDK that will have been captured in the + # capture_internal_exceptions block above. + return was_handled + + if _exception_leads_to_http_5xx(ex, response): + event, hint = event_from_exception( + ex, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "falcon", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + return was_handled + + falcon_app_class._handle_exception = sentry_patched_handle_exception + + +def _patch_prepare_middleware(): + # type: () -> None + original_prepare_middleware = falcon_helpers.prepare_middleware + + def sentry_patched_prepare_middleware( + middleware=None, independent_middleware=False, asgi=False + ): + # type: (Any, Any, bool) -> Any + if asgi: + # We don't support ASGI Falcon apps, so we don't patch anything here + return original_prepare_middleware(middleware, independent_middleware, asgi) + + integration = sentry_sdk.get_client().get_integration(FalconIntegration) + if integration is not None: + middleware = [SentryFalconMiddleware()] + (middleware or []) + + # We intentionally omit the asgi argument here, since the default is False anyways, + # and this way, we remain backwards-compatible with pre-3.0.0 Falcon versions. + return original_prepare_middleware(middleware, independent_middleware) + + falcon_helpers.prepare_middleware = sentry_patched_prepare_middleware + + +def _exception_leads_to_http_5xx(ex, response): + # type: (Exception, falcon.Response) -> bool + is_server_error = isinstance(ex, falcon.HTTPError) and (ex.status or "").startswith( + "5" + ) + is_unhandled_error = not isinstance( + ex, (falcon.HTTPError, falcon.http_status.HTTPStatus) + ) + + # We only check the HTTP status on Falcon 3 because in Falcon 2, the status on the response + # at the stage where we capture it is listed as 200, even though we would expect to see a 500 + # status. Since at the time of this change, Falcon 2 is ca. 4 years old, we have decided to + # only perform this check on Falcon 3+, despite the risk that some handled errors might be + # reported to Sentry as unhandled on Falcon 2. + return (is_server_error or is_unhandled_error) and ( + not FALCON3 or _has_http_5xx_status(response) + ) + + +def _has_http_5xx_status(response): + # type: (falcon.Response) -> bool + return response.status.startswith("5") + + +def _set_transaction_name_and_source(event, transaction_style, request): + # type: (Event, str, falcon.Request) -> None + name_for_style = { + "uri_template": request.uri_template, + "path": request.path, + } + event["transaction"] = name_for_style[transaction_style] + event["transaction_info"] = {"source": SOURCE_FOR_STYLE[transaction_style]} + + +def _make_request_event_processor(req, integration): + # type: (falcon.Request, FalconIntegration) -> EventProcessor + + def event_processor(event, hint): + # type: (Event, dict[str, Any]) -> Event + _set_transaction_name_and_source(event, integration.transaction_style, req) + + with capture_internal_exceptions(): + FalconRequestExtractor(req).extract_into_event(event) + + return event + + return event_processor diff --git a/aws/lambda_demo/sentry_sdk/integrations/fastapi.py b/aws/lambda_demo/sentry_sdk/integrations/fastapi.py new file mode 100644 index 000000000..8877925a3 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/fastapi.py @@ -0,0 +1,147 @@ +import asyncio +from copy import deepcopy +from functools import wraps + +import sentry_sdk +from sentry_sdk.integrations import DidNotEnable +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import SOURCE_FOR_STYLE, TRANSACTION_SOURCE_ROUTE +from sentry_sdk.utils import ( + transaction_from_function, + logger, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Callable, Dict + from sentry_sdk._types import Event + +try: + from sentry_sdk.integrations.starlette import ( + StarletteIntegration, + StarletteRequestExtractor, + ) +except DidNotEnable: + raise DidNotEnable("Starlette is not installed") + +try: + import fastapi # type: ignore +except ImportError: + raise DidNotEnable("FastAPI is not installed") + + +_DEFAULT_TRANSACTION_NAME = "generic FastAPI request" + + +class FastApiIntegration(StarletteIntegration): + identifier = "fastapi" + + @staticmethod + def setup_once(): + # type: () -> None + patch_get_request_handler() + + +def _set_transaction_name_and_source(scope, transaction_style, request): + # type: (sentry_sdk.Scope, str, Any) -> None + name = "" + + if transaction_style == "endpoint": + endpoint = request.scope.get("endpoint") + if endpoint: + name = transaction_from_function(endpoint) or "" + + elif transaction_style == "url": + route = request.scope.get("route") + if route: + path = getattr(route, "path", None) + if path is not None: + name = path + + if not name: + name = _DEFAULT_TRANSACTION_NAME + source = TRANSACTION_SOURCE_ROUTE + else: + source = SOURCE_FOR_STYLE[transaction_style] + + scope.set_transaction_name(name, source=source) + logger.debug( + "[FastAPI] Set transaction name and source on scope: %s / %s", name, source + ) + + +def patch_get_request_handler(): + # type: () -> None + old_get_request_handler = fastapi.routing.get_request_handler + + def _sentry_get_request_handler(*args, **kwargs): + # type: (*Any, **Any) -> Any + dependant = kwargs.get("dependant") + if ( + dependant + and dependant.call is not None + and not asyncio.iscoroutinefunction(dependant.call) + ): + old_call = dependant.call + + @wraps(old_call) + def _sentry_call(*args, **kwargs): + # type: (*Any, **Any) -> Any + current_scope = sentry_sdk.get_current_scope() + if current_scope.transaction is not None: + current_scope.transaction.update_active_thread() + + sentry_scope = sentry_sdk.get_isolation_scope() + if sentry_scope.profile is not None: + sentry_scope.profile.update_active_thread_id() + + return old_call(*args, **kwargs) + + dependant.call = _sentry_call + + old_app = old_get_request_handler(*args, **kwargs) + + async def _sentry_app(*args, **kwargs): + # type: (*Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(FastApiIntegration) + if integration is None: + return await old_app(*args, **kwargs) + + request = args[0] + + _set_transaction_name_and_source( + sentry_sdk.get_current_scope(), integration.transaction_style, request + ) + sentry_scope = sentry_sdk.get_isolation_scope() + extractor = StarletteRequestExtractor(request) + info = await extractor.extract_request_info() + + def _make_request_event_processor(req, integration): + # type: (Any, Any) -> Callable[[Event, Dict[str, Any]], Event] + def event_processor(event, hint): + # type: (Event, Dict[str, Any]) -> Event + + # Extract information from request + request_info = event.get("request", {}) + if info: + if "cookies" in info and should_send_default_pii(): + request_info["cookies"] = info["cookies"] + if "data" in info: + request_info["data"] = info["data"] + event["request"] = deepcopy(request_info) + + return event + + return event_processor + + sentry_scope._name = FastApiIntegration.identifier + sentry_scope.add_event_processor( + _make_request_event_processor(request, integration) + ) + + return await old_app(*args, **kwargs) + + return _sentry_app + + fastapi.routing.get_request_handler = _sentry_get_request_handler diff --git a/aws/lambda_demo/sentry_sdk/integrations/flask.py b/aws/lambda_demo/sentry_sdk/integrations/flask.py new file mode 100644 index 000000000..45b4f0b2b --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/flask.py @@ -0,0 +1,263 @@ +import sentry_sdk +from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration +from sentry_sdk.integrations._wsgi_common import ( + DEFAULT_HTTP_METHODS_TO_CAPTURE, + RequestExtractor, +) +from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import SOURCE_FOR_STYLE +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + package_version, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Callable, Dict, Union + + from sentry_sdk._types import Event, EventProcessor + from sentry_sdk.integrations.wsgi import _ScopedResponse + from werkzeug.datastructures import FileStorage, ImmutableMultiDict + + +try: + import flask_login # type: ignore +except ImportError: + flask_login = None + +try: + from flask import Flask, Request # type: ignore + from flask import request as flask_request + from flask.signals import ( + before_render_template, + got_request_exception, + request_started, + ) + from markupsafe import Markup +except ImportError: + raise DidNotEnable("Flask is not installed") + +try: + import blinker # noqa +except ImportError: + raise DidNotEnable("blinker is not installed") + +TRANSACTION_STYLE_VALUES = ("endpoint", "url") + + +class FlaskIntegration(Integration): + identifier = "flask" + origin = f"auto.http.{identifier}" + + transaction_style = "" + + def __init__( + self, + transaction_style="endpoint", # type: str + http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: tuple[str, ...] + ): + # type: (...) -> None + if transaction_style not in TRANSACTION_STYLE_VALUES: + raise ValueError( + "Invalid value for transaction_style: %s (must be in %s)" + % (transaction_style, TRANSACTION_STYLE_VALUES) + ) + self.transaction_style = transaction_style + self.http_methods_to_capture = tuple(map(str.upper, http_methods_to_capture)) + + @staticmethod + def setup_once(): + # type: () -> None + version = package_version("flask") + _check_minimum_version(FlaskIntegration, version) + + before_render_template.connect(_add_sentry_trace) + request_started.connect(_request_started) + got_request_exception.connect(_capture_exception) + + old_app = Flask.__call__ + + def sentry_patched_wsgi_app(self, environ, start_response): + # type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse + if sentry_sdk.get_client().get_integration(FlaskIntegration) is None: + return old_app(self, environ, start_response) + + integration = sentry_sdk.get_client().get_integration(FlaskIntegration) + + middleware = SentryWsgiMiddleware( + lambda *a, **kw: old_app(self, *a, **kw), + span_origin=FlaskIntegration.origin, + http_methods_to_capture=( + integration.http_methods_to_capture + if integration + else DEFAULT_HTTP_METHODS_TO_CAPTURE + ), + ) + return middleware(environ, start_response) + + Flask.__call__ = sentry_patched_wsgi_app + + +def _add_sentry_trace(sender, template, context, **extra): + # type: (Flask, Any, Dict[str, Any], **Any) -> None + if "sentry_trace" in context: + return + + scope = sentry_sdk.get_current_scope() + trace_meta = Markup(scope.trace_propagation_meta()) + context["sentry_trace"] = trace_meta # for backwards compatibility + context["sentry_trace_meta"] = trace_meta + + +def _set_transaction_name_and_source(scope, transaction_style, request): + # type: (sentry_sdk.Scope, str, Request) -> None + try: + name_for_style = { + "url": request.url_rule.rule, + "endpoint": request.url_rule.endpoint, + } + scope.set_transaction_name( + name_for_style[transaction_style], + source=SOURCE_FOR_STYLE[transaction_style], + ) + except Exception: + pass + + +def _request_started(app, **kwargs): + # type: (Flask, **Any) -> None + integration = sentry_sdk.get_client().get_integration(FlaskIntegration) + if integration is None: + return + + request = flask_request._get_current_object() + + # Set the transaction name and source here, + # but rely on WSGI middleware to actually start the transaction + _set_transaction_name_and_source( + sentry_sdk.get_current_scope(), integration.transaction_style, request + ) + + scope = sentry_sdk.get_isolation_scope() + evt_processor = _make_request_event_processor(app, request, integration) + scope.add_event_processor(evt_processor) + + +class FlaskRequestExtractor(RequestExtractor): + def env(self): + # type: () -> Dict[str, str] + return self.request.environ + + def cookies(self): + # type: () -> Dict[Any, Any] + return { + k: v[0] if isinstance(v, list) and len(v) == 1 else v + for k, v in self.request.cookies.items() + } + + def raw_data(self): + # type: () -> bytes + return self.request.get_data() + + def form(self): + # type: () -> ImmutableMultiDict[str, Any] + return self.request.form + + def files(self): + # type: () -> ImmutableMultiDict[str, Any] + return self.request.files + + def is_json(self): + # type: () -> bool + return self.request.is_json + + def json(self): + # type: () -> Any + return self.request.get_json(silent=True) + + def size_of_file(self, file): + # type: (FileStorage) -> int + return file.content_length + + +def _make_request_event_processor(app, request, integration): + # type: (Flask, Callable[[], Request], FlaskIntegration) -> EventProcessor + + def inner(event, hint): + # type: (Event, dict[str, Any]) -> Event + + # if the request is gone we are fine not logging the data from + # it. This might happen if the processor is pushed away to + # another thread. + if request is None: + return event + + with capture_internal_exceptions(): + FlaskRequestExtractor(request).extract_into_event(event) + + if should_send_default_pii(): + with capture_internal_exceptions(): + _add_user_to_event(event) + + return event + + return inner + + +@ensure_integration_enabled(FlaskIntegration) +def _capture_exception(sender, exception, **kwargs): + # type: (Flask, Union[ValueError, BaseException], **Any) -> None + event, hint = event_from_exception( + exception, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "flask", "handled": False}, + ) + + sentry_sdk.capture_event(event, hint=hint) + + +def _add_user_to_event(event): + # type: (Event) -> None + if flask_login is None: + return + + user = flask_login.current_user + if user is None: + return + + with capture_internal_exceptions(): + # Access this object as late as possible as accessing the user + # is relatively costly + + user_info = event.setdefault("user", {}) + + try: + user_info.setdefault("id", user.get_id()) + # TODO: more configurable user attrs here + except AttributeError: + # might happen if: + # - flask_login could not be imported + # - flask_login is not configured + # - no user is logged in + pass + + # The following attribute accesses are ineffective for the general + # Flask-Login case, because the User interface of Flask-Login does not + # care about anything but the ID. However, Flask-User (based on + # Flask-Login) documents a few optional extra attributes. + # + # https://github.com/lingthio/Flask-User/blob/a379fa0a281789618c484b459cb41236779b95b1/docs/source/data_models.rst#fixed-data-model-property-names + + try: + user_info.setdefault("email", user.email) + except Exception: + pass + + try: + user_info.setdefault("username", user.username) + except Exception: + pass diff --git a/aws/lambda_demo/sentry_sdk/integrations/gcp.py b/aws/lambda_demo/sentry_sdk/integrations/gcp.py new file mode 100644 index 000000000..3983f550d --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/gcp.py @@ -0,0 +1,234 @@ +import functools +import sys +from copy import deepcopy +from datetime import datetime, timedelta, timezone +from os import environ + +import sentry_sdk +from sentry_sdk.api import continue_trace +from sentry_sdk.consts import OP +from sentry_sdk.integrations import Integration +from sentry_sdk.integrations._wsgi_common import _filter_headers +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT +from sentry_sdk.utils import ( + AnnotatedValue, + capture_internal_exceptions, + event_from_exception, + logger, + TimeoutThread, + reraise, +) + +from typing import TYPE_CHECKING + +# Constants +TIMEOUT_WARNING_BUFFER = 1.5 # Buffer time required to send timeout warning to Sentry +MILLIS_TO_SECONDS = 1000.0 + +if TYPE_CHECKING: + from typing import Any + from typing import TypeVar + from typing import Callable + from typing import Optional + + from sentry_sdk._types import EventProcessor, Event, Hint + + F = TypeVar("F", bound=Callable[..., Any]) + + +def _wrap_func(func): + # type: (F) -> F + @functools.wraps(func) + def sentry_func(functionhandler, gcp_event, *args, **kwargs): + # type: (Any, Any, *Any, **Any) -> Any + client = sentry_sdk.get_client() + + integration = client.get_integration(GcpIntegration) + if integration is None: + return func(functionhandler, gcp_event, *args, **kwargs) + + configured_time = environ.get("FUNCTION_TIMEOUT_SEC") + if not configured_time: + logger.debug( + "The configured timeout could not be fetched from Cloud Functions configuration." + ) + return func(functionhandler, gcp_event, *args, **kwargs) + + configured_time = int(configured_time) + + initial_time = datetime.now(timezone.utc) + + with sentry_sdk.isolation_scope() as scope: + with capture_internal_exceptions(): + scope.clear_breadcrumbs() + scope.add_event_processor( + _make_request_event_processor( + gcp_event, configured_time, initial_time + ) + ) + scope.set_tag("gcp_region", environ.get("FUNCTION_REGION")) + timeout_thread = None + if ( + integration.timeout_warning + and configured_time > TIMEOUT_WARNING_BUFFER + ): + waiting_time = configured_time - TIMEOUT_WARNING_BUFFER + + timeout_thread = TimeoutThread(waiting_time, configured_time) + + # Starting the thread to raise timeout warning exception + timeout_thread.start() + + headers = {} + if hasattr(gcp_event, "headers"): + headers = gcp_event.headers + + transaction = continue_trace( + headers, + op=OP.FUNCTION_GCP, + name=environ.get("FUNCTION_NAME", ""), + source=TRANSACTION_SOURCE_COMPONENT, + origin=GcpIntegration.origin, + ) + sampling_context = { + "gcp_env": { + "function_name": environ.get("FUNCTION_NAME"), + "function_entry_point": environ.get("ENTRY_POINT"), + "function_identity": environ.get("FUNCTION_IDENTITY"), + "function_region": environ.get("FUNCTION_REGION"), + "function_project": environ.get("GCP_PROJECT"), + }, + "gcp_event": gcp_event, + } + with sentry_sdk.start_transaction( + transaction, custom_sampling_context=sampling_context + ): + try: + return func(functionhandler, gcp_event, *args, **kwargs) + except Exception: + exc_info = sys.exc_info() + sentry_event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "gcp", "handled": False}, + ) + sentry_sdk.capture_event(sentry_event, hint=hint) + reraise(*exc_info) + finally: + if timeout_thread: + timeout_thread.stop() + # Flush out the event queue + client.flush() + + return sentry_func # type: ignore + + +class GcpIntegration(Integration): + identifier = "gcp" + origin = f"auto.function.{identifier}" + + def __init__(self, timeout_warning=False): + # type: (bool) -> None + self.timeout_warning = timeout_warning + + @staticmethod + def setup_once(): + # type: () -> None + import __main__ as gcp_functions + + if not hasattr(gcp_functions, "worker_v1"): + logger.warning( + "GcpIntegration currently supports only Python 3.7 runtime environment." + ) + return + + worker1 = gcp_functions.worker_v1 + + worker1.FunctionHandler.invoke_user_function = _wrap_func( + worker1.FunctionHandler.invoke_user_function + ) + + +def _make_request_event_processor(gcp_event, configured_timeout, initial_time): + # type: (Any, Any, Any) -> EventProcessor + + def event_processor(event, hint): + # type: (Event, Hint) -> Optional[Event] + + final_time = datetime.now(timezone.utc) + time_diff = final_time - initial_time + + execution_duration_in_millis = time_diff / timedelta(milliseconds=1) + + extra = event.setdefault("extra", {}) + extra["google cloud functions"] = { + "function_name": environ.get("FUNCTION_NAME"), + "function_entry_point": environ.get("ENTRY_POINT"), + "function_identity": environ.get("FUNCTION_IDENTITY"), + "function_region": environ.get("FUNCTION_REGION"), + "function_project": environ.get("GCP_PROJECT"), + "execution_duration_in_millis": execution_duration_in_millis, + "configured_timeout_in_seconds": configured_timeout, + } + + extra["google cloud logs"] = { + "url": _get_google_cloud_logs_url(final_time), + } + + request = event.get("request", {}) + + request["url"] = "gcp:///{}".format(environ.get("FUNCTION_NAME")) + + if hasattr(gcp_event, "method"): + request["method"] = gcp_event.method + + if hasattr(gcp_event, "query_string"): + request["query_string"] = gcp_event.query_string.decode("utf-8") + + if hasattr(gcp_event, "headers"): + request["headers"] = _filter_headers(gcp_event.headers) + + if should_send_default_pii(): + if hasattr(gcp_event, "data"): + request["data"] = gcp_event.data + else: + if hasattr(gcp_event, "data"): + # Unfortunately couldn't find a way to get structured body from GCP + # event. Meaning every body is unstructured to us. + request["data"] = AnnotatedValue.removed_because_raw_data() + + event["request"] = deepcopy(request) + + return event + + return event_processor + + +def _get_google_cloud_logs_url(final_time): + # type: (datetime) -> str + """ + Generates a Google Cloud Logs console URL based on the environment variables + Arguments: + final_time {datetime} -- Final time + Returns: + str -- Google Cloud Logs Console URL to logs. + """ + hour_ago = final_time - timedelta(hours=1) + formatstring = "%Y-%m-%dT%H:%M:%SZ" + + url = ( + "https://console.cloud.google.com/logs/viewer?project={project}&resource=cloud_function" + "%2Ffunction_name%2F{function_name}%2Fregion%2F{region}&minLogLevel=0&expandAll=false" + "×tamp={timestamp_end}&customFacets=&limitCustomFacetWidth=true" + "&dateRangeStart={timestamp_start}&dateRangeEnd={timestamp_end}" + "&interval=PT1H&scrollTimestamp={timestamp_end}" + ).format( + project=environ.get("GCP_PROJECT"), + function_name=environ.get("FUNCTION_NAME"), + region=environ.get("FUNCTION_REGION"), + timestamp_end=final_time.strftime(formatstring), + timestamp_start=hour_ago.strftime(formatstring), + ) + + return url diff --git a/aws/lambda_demo/sentry_sdk/integrations/gnu_backtrace.py b/aws/lambda_demo/sentry_sdk/integrations/gnu_backtrace.py new file mode 100644 index 000000000..dc3dc80fe --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/gnu_backtrace.py @@ -0,0 +1,107 @@ +import re + +import sentry_sdk +from sentry_sdk.integrations import Integration +from sentry_sdk.scope import add_global_event_processor +from sentry_sdk.utils import capture_internal_exceptions + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from sentry_sdk._types import Event + + +MODULE_RE = r"[a-zA-Z0-9/._:\\-]+" +TYPE_RE = r"[a-zA-Z0-9._:<>,-]+" +HEXVAL_RE = r"[A-Fa-f0-9]+" + + +FRAME_RE = r""" +^(?P\d+)\.\s +(?P{MODULE_RE})\( + (?P{TYPE_RE}\ )? + ((?P{TYPE_RE}) + (?P\(.*\))? + )? + ((?P\ const)?\+0x(?P{HEXVAL_RE}))? +\)\s +\[0x(?P{HEXVAL_RE})\]$ +""".format( + MODULE_RE=MODULE_RE, HEXVAL_RE=HEXVAL_RE, TYPE_RE=TYPE_RE +) + +FRAME_RE = re.compile(FRAME_RE, re.MULTILINE | re.VERBOSE) + + +class GnuBacktraceIntegration(Integration): + identifier = "gnu_backtrace" + + @staticmethod + def setup_once(): + # type: () -> None + @add_global_event_processor + def process_gnu_backtrace(event, hint): + # type: (Event, dict[str, Any]) -> Event + with capture_internal_exceptions(): + return _process_gnu_backtrace(event, hint) + + +def _process_gnu_backtrace(event, hint): + # type: (Event, dict[str, Any]) -> Event + if sentry_sdk.get_client().get_integration(GnuBacktraceIntegration) is None: + return event + + exc_info = hint.get("exc_info", None) + + if exc_info is None: + return event + + exception = event.get("exception", None) + + if exception is None: + return event + + values = exception.get("values", None) + + if values is None: + return event + + for exception in values: + frames = exception.get("stacktrace", {}).get("frames", []) + if not frames: + continue + + msg = exception.get("value", None) + if not msg: + continue + + additional_frames = [] + new_msg = [] + + for line in msg.splitlines(): + match = FRAME_RE.match(line) + if match: + additional_frames.append( + ( + int(match.group("index")), + { + "package": match.group("package") or None, + "function": match.group("function") or None, + "platform": "native", + }, + ) + ) + else: + # Put garbage lines back into message, not sure what to do with them. + new_msg.append(line) + + if additional_frames: + additional_frames.sort(key=lambda x: -x[0]) + for _, frame in additional_frames: + frames.append(frame) + + new_msg.append("") + exception["value"] = "\n".join(new_msg) + + return event diff --git a/aws/lambda_demo/sentry_sdk/integrations/gql.py b/aws/lambda_demo/sentry_sdk/integrations/gql.py new file mode 100644 index 000000000..d5341d2cf --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/gql.py @@ -0,0 +1,140 @@ +import sentry_sdk +from sentry_sdk.utils import ( + event_from_exception, + ensure_integration_enabled, + parse_version, +) + +from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration +from sentry_sdk.scope import should_send_default_pii + +try: + import gql # type: ignore[import-not-found] + from graphql import print_ast, get_operation_ast, DocumentNode, VariableDefinitionNode # type: ignore[import-not-found] + from gql.transport import Transport, AsyncTransport # type: ignore[import-not-found] + from gql.transport.exceptions import TransportQueryError # type: ignore[import-not-found] +except ImportError: + raise DidNotEnable("gql is not installed") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Dict, Tuple, Union + from sentry_sdk._types import Event, EventProcessor + + EventDataType = Dict[str, Union[str, Tuple[VariableDefinitionNode, ...]]] + + +class GQLIntegration(Integration): + identifier = "gql" + + @staticmethod + def setup_once(): + # type: () -> None + gql_version = parse_version(gql.__version__) + _check_minimum_version(GQLIntegration, gql_version) + + _patch_execute() + + +def _data_from_document(document): + # type: (DocumentNode) -> EventDataType + try: + operation_ast = get_operation_ast(document) + data = {"query": print_ast(document)} # type: EventDataType + + if operation_ast is not None: + data["variables"] = operation_ast.variable_definitions + if operation_ast.name is not None: + data["operationName"] = operation_ast.name.value + + return data + except (AttributeError, TypeError): + return dict() + + +def _transport_method(transport): + # type: (Union[Transport, AsyncTransport]) -> str + """ + The RequestsHTTPTransport allows defining the HTTP method; all + other transports use POST. + """ + try: + return transport.method + except AttributeError: + return "POST" + + +def _request_info_from_transport(transport): + # type: (Union[Transport, AsyncTransport, None]) -> Dict[str, str] + if transport is None: + return {} + + request_info = { + "method": _transport_method(transport), + } + + try: + request_info["url"] = transport.url + except AttributeError: + pass + + return request_info + + +def _patch_execute(): + # type: () -> None + real_execute = gql.Client.execute + + @ensure_integration_enabled(GQLIntegration, real_execute) + def sentry_patched_execute(self, document, *args, **kwargs): + # type: (gql.Client, DocumentNode, Any, Any) -> Any + scope = sentry_sdk.get_isolation_scope() + scope.add_event_processor(_make_gql_event_processor(self, document)) + + try: + return real_execute(self, document, *args, **kwargs) + except TransportQueryError as e: + event, hint = event_from_exception( + e, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "gql", "handled": False}, + ) + + sentry_sdk.capture_event(event, hint) + raise e + + gql.Client.execute = sentry_patched_execute + + +def _make_gql_event_processor(client, document): + # type: (gql.Client, DocumentNode) -> EventProcessor + def processor(event, hint): + # type: (Event, dict[str, Any]) -> Event + try: + errors = hint["exc_info"][1].errors + except (AttributeError, KeyError): + errors = None + + request = event.setdefault("request", {}) + request.update( + { + "api_target": "graphql", + **_request_info_from_transport(client.transport), + } + ) + + if should_send_default_pii(): + request["data"] = _data_from_document(document) + contexts = event.setdefault("contexts", {}) + response = contexts.setdefault("response", {}) + response.update( + { + "data": {"errors": errors}, + "type": response, + } + ) + + return event + + return processor diff --git a/aws/lambda_demo/sentry_sdk/integrations/graphene.py b/aws/lambda_demo/sentry_sdk/integrations/graphene.py new file mode 100644 index 000000000..198aea50d --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/graphene.py @@ -0,0 +1,151 @@ +from contextlib import contextmanager + +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + package_version, +) + +try: + from graphene.types import schema as graphene_schema # type: ignore +except ImportError: + raise DidNotEnable("graphene is not installed") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Generator + from typing import Any, Dict, Union + from graphene.language.source import Source # type: ignore + from graphql.execution import ExecutionResult # type: ignore + from graphql.type import GraphQLSchema # type: ignore + from sentry_sdk._types import Event + + +class GrapheneIntegration(Integration): + identifier = "graphene" + + @staticmethod + def setup_once(): + # type: () -> None + version = package_version("graphene") + _check_minimum_version(GrapheneIntegration, version) + + _patch_graphql() + + +def _patch_graphql(): + # type: () -> None + old_graphql_sync = graphene_schema.graphql_sync + old_graphql_async = graphene_schema.graphql + + @ensure_integration_enabled(GrapheneIntegration, old_graphql_sync) + def _sentry_patched_graphql_sync(schema, source, *args, **kwargs): + # type: (GraphQLSchema, Union[str, Source], Any, Any) -> ExecutionResult + scope = sentry_sdk.get_isolation_scope() + scope.add_event_processor(_event_processor) + + with graphql_span(schema, source, kwargs): + result = old_graphql_sync(schema, source, *args, **kwargs) + + with capture_internal_exceptions(): + client = sentry_sdk.get_client() + for error in result.errors or []: + event, hint = event_from_exception( + error, + client_options=client.options, + mechanism={ + "type": GrapheneIntegration.identifier, + "handled": False, + }, + ) + sentry_sdk.capture_event(event, hint=hint) + + return result + + async def _sentry_patched_graphql_async(schema, source, *args, **kwargs): + # type: (GraphQLSchema, Union[str, Source], Any, Any) -> ExecutionResult + integration = sentry_sdk.get_client().get_integration(GrapheneIntegration) + if integration is None: + return await old_graphql_async(schema, source, *args, **kwargs) + + scope = sentry_sdk.get_isolation_scope() + scope.add_event_processor(_event_processor) + + with graphql_span(schema, source, kwargs): + result = await old_graphql_async(schema, source, *args, **kwargs) + + with capture_internal_exceptions(): + client = sentry_sdk.get_client() + for error in result.errors or []: + event, hint = event_from_exception( + error, + client_options=client.options, + mechanism={ + "type": GrapheneIntegration.identifier, + "handled": False, + }, + ) + sentry_sdk.capture_event(event, hint=hint) + + return result + + graphene_schema.graphql_sync = _sentry_patched_graphql_sync + graphene_schema.graphql = _sentry_patched_graphql_async + + +def _event_processor(event, hint): + # type: (Event, Dict[str, Any]) -> Event + if should_send_default_pii(): + request_info = event.setdefault("request", {}) + request_info["api_target"] = "graphql" + + elif event.get("request", {}).get("data"): + del event["request"]["data"] + + return event + + +@contextmanager +def graphql_span(schema, source, kwargs): + # type: (GraphQLSchema, Union[str, Source], Dict[str, Any]) -> Generator[None, None, None] + operation_name = kwargs.get("operation_name") + + operation_type = "query" + op = OP.GRAPHQL_QUERY + if source.strip().startswith("mutation"): + operation_type = "mutation" + op = OP.GRAPHQL_MUTATION + elif source.strip().startswith("subscription"): + operation_type = "subscription" + op = OP.GRAPHQL_SUBSCRIPTION + + sentry_sdk.add_breadcrumb( + crumb={ + "data": { + "operation_name": operation_name, + "operation_type": operation_type, + }, + "category": "graphql.operation", + }, + ) + + scope = sentry_sdk.get_current_scope() + if scope.span: + _graphql_span = scope.span.start_child(op=op, name=operation_name) + else: + _graphql_span = sentry_sdk.start_span(op=op, name=operation_name) + + _graphql_span.set_data("graphql.document", source) + _graphql_span.set_data("graphql.operation.name", operation_name) + _graphql_span.set_data("graphql.operation.type", operation_type) + + try: + yield + finally: + _graphql_span.finish() diff --git a/aws/lambda_demo/sentry_sdk/integrations/grpc/__init__.py b/aws/lambda_demo/sentry_sdk/integrations/grpc/__init__.py new file mode 100644 index 000000000..3d949091e --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/grpc/__init__.py @@ -0,0 +1,151 @@ +from functools import wraps + +import grpc +from grpc import Channel, Server, intercept_channel +from grpc.aio import Channel as AsyncChannel +from grpc.aio import Server as AsyncServer + +from sentry_sdk.integrations import Integration + +from .client import ClientInterceptor +from .server import ServerInterceptor +from .aio.server import ServerInterceptor as AsyncServerInterceptor +from .aio.client import ( + SentryUnaryUnaryClientInterceptor as AsyncUnaryUnaryClientInterceptor, +) +from .aio.client import ( + SentryUnaryStreamClientInterceptor as AsyncUnaryStreamClientIntercetor, +) + +from typing import TYPE_CHECKING, Any, Optional, Sequence + +# Hack to get new Python features working in older versions +# without introducing a hard dependency on `typing_extensions` +# from: https://stackoverflow.com/a/71944042/300572 +if TYPE_CHECKING: + from typing import ParamSpec, Callable +else: + # Fake ParamSpec + class ParamSpec: + def __init__(self, _): + self.args = None + self.kwargs = None + + # Callable[anything] will return None + class _Callable: + def __getitem__(self, _): + return None + + # Make instances + Callable = _Callable() + +P = ParamSpec("P") + + +def _wrap_channel_sync(func: Callable[P, Channel]) -> Callable[P, Channel]: + "Wrapper for synchronous secure and insecure channel." + + @wraps(func) + def patched_channel(*args: Any, **kwargs: Any) -> Channel: + channel = func(*args, **kwargs) + if not ClientInterceptor._is_intercepted: + ClientInterceptor._is_intercepted = True + return intercept_channel(channel, ClientInterceptor()) + else: + return channel + + return patched_channel + + +def _wrap_intercept_channel(func: Callable[P, Channel]) -> Callable[P, Channel]: + @wraps(func) + def patched_intercept_channel( + channel: Channel, *interceptors: grpc.ServerInterceptor + ) -> Channel: + if ClientInterceptor._is_intercepted: + interceptors = tuple( + [ + interceptor + for interceptor in interceptors + if not isinstance(interceptor, ClientInterceptor) + ] + ) + else: + interceptors = interceptors + return intercept_channel(channel, *interceptors) + + return patched_intercept_channel # type: ignore + + +def _wrap_channel_async(func: Callable[P, AsyncChannel]) -> Callable[P, AsyncChannel]: + "Wrapper for asynchronous secure and insecure channel." + + @wraps(func) + def patched_channel( + *args: P.args, + interceptors: Optional[Sequence[grpc.aio.ClientInterceptor]] = None, + **kwargs: P.kwargs, + ) -> Channel: + sentry_interceptors = [ + AsyncUnaryUnaryClientInterceptor(), + AsyncUnaryStreamClientIntercetor(), + ] + interceptors = [*sentry_interceptors, *(interceptors or [])] + return func(*args, interceptors=interceptors, **kwargs) # type: ignore + + return patched_channel # type: ignore + + +def _wrap_sync_server(func: Callable[P, Server]) -> Callable[P, Server]: + """Wrapper for synchronous server.""" + + @wraps(func) + def patched_server( + *args: P.args, + interceptors: Optional[Sequence[grpc.ServerInterceptor]] = None, + **kwargs: P.kwargs, + ) -> Server: + interceptors = [ + interceptor + for interceptor in interceptors or [] + if not isinstance(interceptor, ServerInterceptor) + ] + server_interceptor = ServerInterceptor() + interceptors = [server_interceptor, *(interceptors or [])] + return func(*args, interceptors=interceptors, **kwargs) # type: ignore + + return patched_server # type: ignore + + +def _wrap_async_server(func: Callable[P, AsyncServer]) -> Callable[P, AsyncServer]: + """Wrapper for asynchronous server.""" + + @wraps(func) + def patched_aio_server( + *args: P.args, + interceptors: Optional[Sequence[grpc.ServerInterceptor]] = None, + **kwargs: P.kwargs, + ) -> Server: + server_interceptor = AsyncServerInterceptor() + interceptors = (server_interceptor, *(interceptors or [])) + return func(*args, interceptors=interceptors, **kwargs) # type: ignore + + return patched_aio_server # type: ignore + + +class GRPCIntegration(Integration): + identifier = "grpc" + + @staticmethod + def setup_once() -> None: + import grpc + + grpc.insecure_channel = _wrap_channel_sync(grpc.insecure_channel) + grpc.secure_channel = _wrap_channel_sync(grpc.secure_channel) + grpc.intercept_channel = _wrap_intercept_channel(grpc.intercept_channel) + + grpc.aio.insecure_channel = _wrap_channel_async(grpc.aio.insecure_channel) + grpc.aio.secure_channel = _wrap_channel_async(grpc.aio.secure_channel) + + grpc.server = _wrap_sync_server(grpc.server) + grpc.aio.server = _wrap_async_server(grpc.aio.server) diff --git a/aws/lambda_demo/sentry_sdk/integrations/grpc/aio/__init__.py b/aws/lambda_demo/sentry_sdk/integrations/grpc/aio/__init__.py new file mode 100644 index 000000000..5b9e3b994 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/grpc/aio/__init__.py @@ -0,0 +1,7 @@ +from .server import ServerInterceptor +from .client import ClientInterceptor + +__all__ = [ + "ClientInterceptor", + "ServerInterceptor", +] diff --git a/aws/lambda_demo/sentry_sdk/integrations/grpc/aio/client.py b/aws/lambda_demo/sentry_sdk/integrations/grpc/aio/client.py new file mode 100644 index 000000000..ff3c21317 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/grpc/aio/client.py @@ -0,0 +1,94 @@ +from typing import Callable, Union, AsyncIterable, Any + +from grpc.aio import ( + UnaryUnaryClientInterceptor, + UnaryStreamClientInterceptor, + ClientCallDetails, + UnaryUnaryCall, + UnaryStreamCall, + Metadata, +) +from google.protobuf.message import Message + +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN + + +class ClientInterceptor: + @staticmethod + def _update_client_call_details_metadata_from_scope( + client_call_details: ClientCallDetails, + ) -> ClientCallDetails: + if client_call_details.metadata is None: + client_call_details = client_call_details._replace(metadata=Metadata()) + elif not isinstance(client_call_details.metadata, Metadata): + # This is a workaround for a GRPC bug, which was fixed in grpcio v1.60.0 + # See https://github.com/grpc/grpc/issues/34298. + client_call_details = client_call_details._replace( + metadata=Metadata.from_tuple(client_call_details.metadata) + ) + for ( + key, + value, + ) in sentry_sdk.get_current_scope().iter_trace_propagation_headers(): + client_call_details.metadata.add(key, value) + return client_call_details + + +class SentryUnaryUnaryClientInterceptor(ClientInterceptor, UnaryUnaryClientInterceptor): # type: ignore + async def intercept_unary_unary( + self, + continuation: Callable[[ClientCallDetails, Message], UnaryUnaryCall], + client_call_details: ClientCallDetails, + request: Message, + ) -> Union[UnaryUnaryCall, Message]: + method = client_call_details.method + + with sentry_sdk.start_span( + op=OP.GRPC_CLIENT, + name="unary unary call to %s" % method.decode(), + origin=SPAN_ORIGIN, + ) as span: + span.set_data("type", "unary unary") + span.set_data("method", method) + + client_call_details = self._update_client_call_details_metadata_from_scope( + client_call_details + ) + + response = await continuation(client_call_details, request) + status_code = await response.code() + span.set_data("code", status_code.name) + + return response + + +class SentryUnaryStreamClientInterceptor( + ClientInterceptor, UnaryStreamClientInterceptor # type: ignore +): + async def intercept_unary_stream( + self, + continuation: Callable[[ClientCallDetails, Message], UnaryStreamCall], + client_call_details: ClientCallDetails, + request: Message, + ) -> Union[AsyncIterable[Any], UnaryStreamCall]: + method = client_call_details.method + + with sentry_sdk.start_span( + op=OP.GRPC_CLIENT, + name="unary stream call to %s" % method.decode(), + origin=SPAN_ORIGIN, + ) as span: + span.set_data("type", "unary stream") + span.set_data("method", method) + + client_call_details = self._update_client_call_details_metadata_from_scope( + client_call_details + ) + + response = await continuation(client_call_details, request) + # status_code = await response.code() + # span.set_data("code", status_code) + + return response diff --git a/aws/lambda_demo/sentry_sdk/integrations/grpc/aio/server.py b/aws/lambda_demo/sentry_sdk/integrations/grpc/aio/server.py new file mode 100644 index 000000000..addc6bee3 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/grpc/aio/server.py @@ -0,0 +1,100 @@ +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.integrations import DidNotEnable +from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN +from sentry_sdk.tracing import Transaction, TRANSACTION_SOURCE_CUSTOM +from sentry_sdk.utils import event_from_exception + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Awaitable, Callable + from typing import Any, Optional + + +try: + import grpc + from grpc import HandlerCallDetails, RpcMethodHandler + from grpc.aio import AbortError, ServicerContext +except ImportError: + raise DidNotEnable("grpcio is not installed") + + +class ServerInterceptor(grpc.aio.ServerInterceptor): # type: ignore + def __init__(self, find_name=None): + # type: (ServerInterceptor, Callable[[ServicerContext], str] | None) -> None + self._find_method_name = find_name or self._find_name + + super().__init__() + + async def intercept_service(self, continuation, handler_call_details): + # type: (ServerInterceptor, Callable[[HandlerCallDetails], Awaitable[RpcMethodHandler]], HandlerCallDetails) -> Optional[Awaitable[RpcMethodHandler]] + self._handler_call_details = handler_call_details + handler = await continuation(handler_call_details) + if handler is None: + return None + + if not handler.request_streaming and not handler.response_streaming: + handler_factory = grpc.unary_unary_rpc_method_handler + + async def wrapped(request, context): + # type: (Any, ServicerContext) -> Any + name = self._find_method_name(context) + if not name: + return await handler(request, context) + + # What if the headers are empty? + transaction = Transaction.continue_from_headers( + dict(context.invocation_metadata()), + op=OP.GRPC_SERVER, + name=name, + source=TRANSACTION_SOURCE_CUSTOM, + origin=SPAN_ORIGIN, + ) + + with sentry_sdk.start_transaction(transaction=transaction): + try: + return await handler.unary_unary(request, context) + except AbortError: + raise + except Exception as exc: + event, hint = event_from_exception( + exc, + mechanism={"type": "grpc", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + raise + + elif not handler.request_streaming and handler.response_streaming: + handler_factory = grpc.unary_stream_rpc_method_handler + + async def wrapped(request, context): # type: ignore + # type: (Any, ServicerContext) -> Any + async for r in handler.unary_stream(request, context): + yield r + + elif handler.request_streaming and not handler.response_streaming: + handler_factory = grpc.stream_unary_rpc_method_handler + + async def wrapped(request, context): + # type: (Any, ServicerContext) -> Any + response = handler.stream_unary(request, context) + return await response + + elif handler.request_streaming and handler.response_streaming: + handler_factory = grpc.stream_stream_rpc_method_handler + + async def wrapped(request, context): # type: ignore + # type: (Any, ServicerContext) -> Any + async for r in handler.stream_stream(request, context): + yield r + + return handler_factory( + wrapped, + request_deserializer=handler.request_deserializer, + response_serializer=handler.response_serializer, + ) + + def _find_name(self, context): + # type: (ServicerContext) -> str + return self._handler_call_details.method diff --git a/aws/lambda_demo/sentry_sdk/integrations/grpc/client.py b/aws/lambda_demo/sentry_sdk/integrations/grpc/client.py new file mode 100644 index 000000000..a5b4f9f52 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/grpc/client.py @@ -0,0 +1,92 @@ +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.integrations import DidNotEnable +from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Callable, Iterator, Iterable, Union + +try: + import grpc + from grpc import ClientCallDetails, Call + from grpc._interceptor import _UnaryOutcome + from grpc.aio._interceptor import UnaryStreamCall + from google.protobuf.message import Message +except ImportError: + raise DidNotEnable("grpcio is not installed") + + +class ClientInterceptor( + grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInterceptor # type: ignore +): + _is_intercepted = False + + def intercept_unary_unary(self, continuation, client_call_details, request): + # type: (ClientInterceptor, Callable[[ClientCallDetails, Message], _UnaryOutcome], ClientCallDetails, Message) -> _UnaryOutcome + method = client_call_details.method + + with sentry_sdk.start_span( + op=OP.GRPC_CLIENT, + name="unary unary call to %s" % method, + origin=SPAN_ORIGIN, + ) as span: + span.set_data("type", "unary unary") + span.set_data("method", method) + + client_call_details = self._update_client_call_details_metadata_from_scope( + client_call_details + ) + + response = continuation(client_call_details, request) + span.set_data("code", response.code().name) + + return response + + def intercept_unary_stream(self, continuation, client_call_details, request): + # type: (ClientInterceptor, Callable[[ClientCallDetails, Message], Union[Iterable[Any], UnaryStreamCall]], ClientCallDetails, Message) -> Union[Iterator[Message], Call] + method = client_call_details.method + + with sentry_sdk.start_span( + op=OP.GRPC_CLIENT, + name="unary stream call to %s" % method, + origin=SPAN_ORIGIN, + ) as span: + span.set_data("type", "unary stream") + span.set_data("method", method) + + client_call_details = self._update_client_call_details_metadata_from_scope( + client_call_details + ) + + response = continuation( + client_call_details, request + ) # type: UnaryStreamCall + # Setting code on unary-stream leads to execution getting stuck + # span.set_data("code", response.code().name) + + return response + + @staticmethod + def _update_client_call_details_metadata_from_scope(client_call_details): + # type: (ClientCallDetails) -> ClientCallDetails + metadata = ( + list(client_call_details.metadata) if client_call_details.metadata else [] + ) + for ( + key, + value, + ) in sentry_sdk.get_current_scope().iter_trace_propagation_headers(): + metadata.append((key, value)) + + client_call_details = grpc._interceptor._ClientCallDetails( + method=client_call_details.method, + timeout=client_call_details.timeout, + metadata=metadata, + credentials=client_call_details.credentials, + wait_for_ready=client_call_details.wait_for_ready, + compression=client_call_details.compression, + ) + + return client_call_details diff --git a/aws/lambda_demo/sentry_sdk/integrations/grpc/consts.py b/aws/lambda_demo/sentry_sdk/integrations/grpc/consts.py new file mode 100644 index 000000000..9fdb975ca --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/grpc/consts.py @@ -0,0 +1 @@ +SPAN_ORIGIN = "auto.grpc.grpc" diff --git a/aws/lambda_demo/sentry_sdk/integrations/grpc/server.py b/aws/lambda_demo/sentry_sdk/integrations/grpc/server.py new file mode 100644 index 000000000..a640df5e1 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/grpc/server.py @@ -0,0 +1,66 @@ +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.integrations import DidNotEnable +from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN +from sentry_sdk.tracing import Transaction, TRANSACTION_SOURCE_CUSTOM + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Callable, Optional + from google.protobuf.message import Message + +try: + import grpc + from grpc import ServicerContext, HandlerCallDetails, RpcMethodHandler +except ImportError: + raise DidNotEnable("grpcio is not installed") + + +class ServerInterceptor(grpc.ServerInterceptor): # type: ignore + def __init__(self, find_name=None): + # type: (ServerInterceptor, Optional[Callable[[ServicerContext], str]]) -> None + self._find_method_name = find_name or ServerInterceptor._find_name + + super().__init__() + + def intercept_service(self, continuation, handler_call_details): + # type: (ServerInterceptor, Callable[[HandlerCallDetails], RpcMethodHandler], HandlerCallDetails) -> RpcMethodHandler + handler = continuation(handler_call_details) + if not handler or not handler.unary_unary: + return handler + + def behavior(request, context): + # type: (Message, ServicerContext) -> Message + with sentry_sdk.isolation_scope(): + name = self._find_method_name(context) + + if name: + metadata = dict(context.invocation_metadata()) + + transaction = Transaction.continue_from_headers( + metadata, + op=OP.GRPC_SERVER, + name=name, + source=TRANSACTION_SOURCE_CUSTOM, + origin=SPAN_ORIGIN, + ) + + with sentry_sdk.start_transaction(transaction=transaction): + try: + return handler.unary_unary(request, context) + except BaseException as e: + raise e + else: + return handler.unary_unary(request, context) + + return grpc.unary_unary_rpc_method_handler( + behavior, + request_deserializer=handler.request_deserializer, + response_serializer=handler.response_serializer, + ) + + @staticmethod + def _find_name(context): + # type: (ServicerContext) -> str + return context._rpc_event.call_details.method.decode() diff --git a/aws/lambda_demo/sentry_sdk/integrations/httpx.py b/aws/lambda_demo/sentry_sdk/integrations/httpx.py new file mode 100644 index 000000000..2ddd44489 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/httpx.py @@ -0,0 +1,167 @@ +import sentry_sdk +from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.integrations import Integration, DidNotEnable +from sentry_sdk.tracing import BAGGAGE_HEADER_NAME +from sentry_sdk.tracing_utils import Baggage, should_propagate_trace +from sentry_sdk.utils import ( + SENSITIVE_DATA_SUBSTITUTE, + capture_internal_exceptions, + ensure_integration_enabled, + logger, + parse_url, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import MutableMapping + from typing import Any + + +try: + from httpx import AsyncClient, Client, Request, Response # type: ignore +except ImportError: + raise DidNotEnable("httpx is not installed") + +__all__ = ["HttpxIntegration"] + + +class HttpxIntegration(Integration): + identifier = "httpx" + origin = f"auto.http.{identifier}" + + @staticmethod + def setup_once(): + # type: () -> None + """ + httpx has its own transport layer and can be customized when needed, + so patch Client.send and AsyncClient.send to support both synchronous and async interfaces. + """ + _install_httpx_client() + _install_httpx_async_client() + + +def _install_httpx_client(): + # type: () -> None + real_send = Client.send + + @ensure_integration_enabled(HttpxIntegration, real_send) + def send(self, request, **kwargs): + # type: (Client, Request, **Any) -> Response + parsed_url = None + with capture_internal_exceptions(): + parsed_url = parse_url(str(request.url), sanitize=False) + + with sentry_sdk.start_span( + op=OP.HTTP_CLIENT, + name="%s %s" + % ( + request.method, + parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE, + ), + origin=HttpxIntegration.origin, + ) as span: + span.set_data(SPANDATA.HTTP_METHOD, request.method) + if parsed_url is not None: + span.set_data("url", parsed_url.url) + span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query) + span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment) + + if should_propagate_trace(sentry_sdk.get_client(), str(request.url)): + for ( + key, + value, + ) in sentry_sdk.get_current_scope().iter_trace_propagation_headers(): + logger.debug( + "[Tracing] Adding `{key}` header {value} to outgoing request to {url}.".format( + key=key, value=value, url=request.url + ) + ) + + if key == BAGGAGE_HEADER_NAME: + _add_sentry_baggage_to_headers(request.headers, value) + else: + request.headers[key] = value + + rv = real_send(self, request, **kwargs) + + span.set_http_status(rv.status_code) + span.set_data("reason", rv.reason_phrase) + + return rv + + Client.send = send + + +def _install_httpx_async_client(): + # type: () -> None + real_send = AsyncClient.send + + async def send(self, request, **kwargs): + # type: (AsyncClient, Request, **Any) -> Response + if sentry_sdk.get_client().get_integration(HttpxIntegration) is None: + return await real_send(self, request, **kwargs) + + parsed_url = None + with capture_internal_exceptions(): + parsed_url = parse_url(str(request.url), sanitize=False) + + with sentry_sdk.start_span( + op=OP.HTTP_CLIENT, + name="%s %s" + % ( + request.method, + parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE, + ), + origin=HttpxIntegration.origin, + ) as span: + span.set_data(SPANDATA.HTTP_METHOD, request.method) + if parsed_url is not None: + span.set_data("url", parsed_url.url) + span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query) + span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment) + + if should_propagate_trace(sentry_sdk.get_client(), str(request.url)): + for ( + key, + value, + ) in sentry_sdk.get_current_scope().iter_trace_propagation_headers(): + logger.debug( + "[Tracing] Adding `{key}` header {value} to outgoing request to {url}.".format( + key=key, value=value, url=request.url + ) + ) + if key == BAGGAGE_HEADER_NAME and request.headers.get( + BAGGAGE_HEADER_NAME + ): + # do not overwrite any existing baggage, just append to it + request.headers[key] += "," + value + else: + request.headers[key] = value + + rv = await real_send(self, request, **kwargs) + + span.set_http_status(rv.status_code) + span.set_data("reason", rv.reason_phrase) + + return rv + + AsyncClient.send = send + + +def _add_sentry_baggage_to_headers(headers, sentry_baggage): + # type: (MutableMapping[str, str], str) -> None + """Add the Sentry baggage to the headers. + + This function directly mutates the provided headers. The provided sentry_baggage + is appended to the existing baggage. If the baggage already contains Sentry items, + they are stripped out first. + """ + existing_baggage = headers.get(BAGGAGE_HEADER_NAME, "") + stripped_existing_baggage = Baggage.strip_sentry_baggage(existing_baggage) + + separator = "," if len(stripped_existing_baggage) > 0 else "" + + headers[BAGGAGE_HEADER_NAME] = ( + stripped_existing_baggage + separator + sentry_baggage + ) diff --git a/aws/lambda_demo/sentry_sdk/integrations/huey.py b/aws/lambda_demo/sentry_sdk/integrations/huey.py new file mode 100644 index 000000000..7db57680f --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/huey.py @@ -0,0 +1,174 @@ +import sys +from datetime import datetime + +import sentry_sdk +from sentry_sdk.api import continue_trace, get_baggage, get_traceparent +from sentry_sdk.consts import OP, SPANSTATUS +from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import ( + BAGGAGE_HEADER_NAME, + SENTRY_TRACE_HEADER_NAME, + TRANSACTION_SOURCE_TASK, +) +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + SENSITIVE_DATA_SUBSTITUTE, + reraise, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Callable, Optional, Union, TypeVar + + from sentry_sdk._types import EventProcessor, Event, Hint + from sentry_sdk.utils import ExcInfo + + F = TypeVar("F", bound=Callable[..., Any]) + +try: + from huey.api import Huey, Result, ResultGroup, Task, PeriodicTask + from huey.exceptions import CancelExecution, RetryTask, TaskLockedException +except ImportError: + raise DidNotEnable("Huey is not installed") + + +HUEY_CONTROL_FLOW_EXCEPTIONS = (CancelExecution, RetryTask, TaskLockedException) + + +class HueyIntegration(Integration): + identifier = "huey" + origin = f"auto.queue.{identifier}" + + @staticmethod + def setup_once(): + # type: () -> None + patch_enqueue() + patch_execute() + + +def patch_enqueue(): + # type: () -> None + old_enqueue = Huey.enqueue + + @ensure_integration_enabled(HueyIntegration, old_enqueue) + def _sentry_enqueue(self, task): + # type: (Huey, Task) -> Optional[Union[Result, ResultGroup]] + with sentry_sdk.start_span( + op=OP.QUEUE_SUBMIT_HUEY, + name=task.name, + origin=HueyIntegration.origin, + ): + if not isinstance(task, PeriodicTask): + # Attach trace propagation data to task kwargs. We do + # not do this for periodic tasks, as these don't + # really have an originating transaction. + task.kwargs["sentry_headers"] = { + BAGGAGE_HEADER_NAME: get_baggage(), + SENTRY_TRACE_HEADER_NAME: get_traceparent(), + } + return old_enqueue(self, task) + + Huey.enqueue = _sentry_enqueue + + +def _make_event_processor(task): + # type: (Any) -> EventProcessor + def event_processor(event, hint): + # type: (Event, Hint) -> Optional[Event] + + with capture_internal_exceptions(): + tags = event.setdefault("tags", {}) + tags["huey_task_id"] = task.id + tags["huey_task_retry"] = task.default_retries > task.retries + extra = event.setdefault("extra", {}) + extra["huey-job"] = { + "task": task.name, + "args": ( + task.args + if should_send_default_pii() + else SENSITIVE_DATA_SUBSTITUTE + ), + "kwargs": ( + task.kwargs + if should_send_default_pii() + else SENSITIVE_DATA_SUBSTITUTE + ), + "retry": (task.default_retries or 0) - task.retries, + } + + return event + + return event_processor + + +def _capture_exception(exc_info): + # type: (ExcInfo) -> None + scope = sentry_sdk.get_current_scope() + + if exc_info[0] in HUEY_CONTROL_FLOW_EXCEPTIONS: + scope.transaction.set_status(SPANSTATUS.ABORTED) + return + + scope.transaction.set_status(SPANSTATUS.INTERNAL_ERROR) + event, hint = event_from_exception( + exc_info, + client_options=sentry_sdk.get_client().options, + mechanism={"type": HueyIntegration.identifier, "handled": False}, + ) + scope.capture_event(event, hint=hint) + + +def _wrap_task_execute(func): + # type: (F) -> F + + @ensure_integration_enabled(HueyIntegration, func) + def _sentry_execute(*args, **kwargs): + # type: (*Any, **Any) -> Any + try: + result = func(*args, **kwargs) + except Exception: + exc_info = sys.exc_info() + _capture_exception(exc_info) + reraise(*exc_info) + + return result + + return _sentry_execute # type: ignore + + +def patch_execute(): + # type: () -> None + old_execute = Huey._execute + + @ensure_integration_enabled(HueyIntegration, old_execute) + def _sentry_execute(self, task, timestamp=None): + # type: (Huey, Task, Optional[datetime]) -> Any + with sentry_sdk.isolation_scope() as scope: + with capture_internal_exceptions(): + scope._name = "huey" + scope.clear_breadcrumbs() + scope.add_event_processor(_make_event_processor(task)) + + sentry_headers = task.kwargs.pop("sentry_headers", None) + + transaction = continue_trace( + sentry_headers or {}, + name=task.name, + op=OP.QUEUE_TASK_HUEY, + source=TRANSACTION_SOURCE_TASK, + origin=HueyIntegration.origin, + ) + transaction.set_status(SPANSTATUS.OK) + + if not getattr(task, "_sentry_is_patched", False): + task.execute = _wrap_task_execute(task.execute) + task._sentry_is_patched = True + + with sentry_sdk.start_transaction(transaction): + return old_execute(self, task, timestamp) + + Huey._execute = _sentry_execute diff --git a/aws/lambda_demo/sentry_sdk/integrations/huggingface_hub.py b/aws/lambda_demo/sentry_sdk/integrations/huggingface_hub.py new file mode 100644 index 000000000..d09f6e216 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/huggingface_hub.py @@ -0,0 +1,175 @@ +from functools import wraps + +from sentry_sdk import consts +from sentry_sdk.ai.monitoring import record_token_usage +from sentry_sdk.ai.utils import set_data_normalized +from sentry_sdk.consts import SPANDATA + +from typing import Any, Iterable, Callable + +import sentry_sdk +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.utils import ( + capture_internal_exceptions, + event_from_exception, +) + +try: + import huggingface_hub.inference._client + + from huggingface_hub import ChatCompletionStreamOutput, TextGenerationOutput +except ImportError: + raise DidNotEnable("Huggingface not installed") + + +class HuggingfaceHubIntegration(Integration): + identifier = "huggingface_hub" + origin = f"auto.ai.{identifier}" + + def __init__(self, include_prompts=True): + # type: (HuggingfaceHubIntegration, bool) -> None + self.include_prompts = include_prompts + + @staticmethod + def setup_once(): + # type: () -> None + huggingface_hub.inference._client.InferenceClient.text_generation = ( + _wrap_text_generation( + huggingface_hub.inference._client.InferenceClient.text_generation + ) + ) + + +def _capture_exception(exc): + # type: (Any) -> None + event, hint = event_from_exception( + exc, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "huggingface_hub", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + +def _wrap_text_generation(f): + # type: (Callable[..., Any]) -> Callable[..., Any] + @wraps(f) + def new_text_generation(*args, **kwargs): + # type: (*Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(HuggingfaceHubIntegration) + if integration is None: + return f(*args, **kwargs) + + if "prompt" in kwargs: + prompt = kwargs["prompt"] + elif len(args) >= 2: + kwargs["prompt"] = args[1] + prompt = kwargs["prompt"] + args = (args[0],) + args[2:] + else: + # invalid call, let it return error + return f(*args, **kwargs) + + model = kwargs.get("model") + streaming = kwargs.get("stream") + + span = sentry_sdk.start_span( + op=consts.OP.HUGGINGFACE_HUB_CHAT_COMPLETIONS_CREATE, + name="Text Generation", + origin=HuggingfaceHubIntegration.origin, + ) + span.__enter__() + try: + res = f(*args, **kwargs) + except Exception as e: + _capture_exception(e) + span.__exit__(None, None, None) + raise e from None + + with capture_internal_exceptions(): + if should_send_default_pii() and integration.include_prompts: + set_data_normalized(span, SPANDATA.AI_INPUT_MESSAGES, prompt) + + set_data_normalized(span, SPANDATA.AI_MODEL_ID, model) + set_data_normalized(span, SPANDATA.AI_STREAMING, streaming) + + if isinstance(res, str): + if should_send_default_pii() and integration.include_prompts: + set_data_normalized( + span, + "ai.responses", + [res], + ) + span.__exit__(None, None, None) + return res + + if isinstance(res, TextGenerationOutput): + if should_send_default_pii() and integration.include_prompts: + set_data_normalized( + span, + "ai.responses", + [res.generated_text], + ) + if res.details is not None and res.details.generated_tokens > 0: + record_token_usage(span, total_tokens=res.details.generated_tokens) + span.__exit__(None, None, None) + return res + + if not isinstance(res, Iterable): + # we only know how to deal with strings and iterables, ignore + set_data_normalized(span, "unknown_response", True) + span.__exit__(None, None, None) + return res + + if kwargs.get("details", False): + # res is Iterable[TextGenerationStreamOutput] + def new_details_iterator(): + # type: () -> Iterable[ChatCompletionStreamOutput] + with capture_internal_exceptions(): + tokens_used = 0 + data_buf: list[str] = [] + for x in res: + if hasattr(x, "token") and hasattr(x.token, "text"): + data_buf.append(x.token.text) + if hasattr(x, "details") and hasattr( + x.details, "generated_tokens" + ): + tokens_used = x.details.generated_tokens + yield x + if ( + len(data_buf) > 0 + and should_send_default_pii() + and integration.include_prompts + ): + set_data_normalized( + span, SPANDATA.AI_RESPONSES, "".join(data_buf) + ) + if tokens_used > 0: + record_token_usage(span, total_tokens=tokens_used) + span.__exit__(None, None, None) + + return new_details_iterator() + else: + # res is Iterable[str] + + def new_iterator(): + # type: () -> Iterable[str] + data_buf: list[str] = [] + with capture_internal_exceptions(): + for s in res: + if isinstance(s, str): + data_buf.append(s) + yield s + if ( + len(data_buf) > 0 + and should_send_default_pii() + and integration.include_prompts + ): + set_data_normalized( + span, SPANDATA.AI_RESPONSES, "".join(data_buf) + ) + span.__exit__(None, None, None) + + return new_iterator() + + return new_text_generation diff --git a/aws/lambda_demo/sentry_sdk/integrations/langchain.py b/aws/lambda_demo/sentry_sdk/integrations/langchain.py new file mode 100644 index 000000000..431fc46be --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/langchain.py @@ -0,0 +1,465 @@ +from collections import OrderedDict +from functools import wraps + +import sentry_sdk +from sentry_sdk.ai.monitoring import set_ai_pipeline_name, record_token_usage +from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.ai.utils import set_data_normalized +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import Span +from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.utils import logger, capture_internal_exceptions + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, List, Callable, Dict, Union, Optional + from uuid import UUID + +try: + from langchain_core.messages import BaseMessage + from langchain_core.outputs import LLMResult + from langchain_core.callbacks import ( + manager, + BaseCallbackHandler, + ) + from langchain_core.agents import AgentAction, AgentFinish +except ImportError: + raise DidNotEnable("langchain not installed") + + +DATA_FIELDS = { + "temperature": SPANDATA.AI_TEMPERATURE, + "top_p": SPANDATA.AI_TOP_P, + "top_k": SPANDATA.AI_TOP_K, + "function_call": SPANDATA.AI_FUNCTION_CALL, + "tool_calls": SPANDATA.AI_TOOL_CALLS, + "tools": SPANDATA.AI_TOOLS, + "response_format": SPANDATA.AI_RESPONSE_FORMAT, + "logit_bias": SPANDATA.AI_LOGIT_BIAS, + "tags": SPANDATA.AI_TAGS, +} + +# To avoid double collecting tokens, we do *not* measure +# token counts for models for which we have an explicit integration +NO_COLLECT_TOKEN_MODELS = [ + "openai-chat", + "anthropic-chat", + "cohere-chat", + "huggingface_endpoint", +] + + +class LangchainIntegration(Integration): + identifier = "langchain" + origin = f"auto.ai.{identifier}" + + # The most number of spans (e.g., LLM calls) that can be processed at the same time. + max_spans = 1024 + + def __init__( + self, include_prompts=True, max_spans=1024, tiktoken_encoding_name=None + ): + # type: (LangchainIntegration, bool, int, Optional[str]) -> None + self.include_prompts = include_prompts + self.max_spans = max_spans + self.tiktoken_encoding_name = tiktoken_encoding_name + + @staticmethod + def setup_once(): + # type: () -> None + manager._configure = _wrap_configure(manager._configure) + + +class WatchedSpan: + span = None # type: Span + num_completion_tokens = 0 # type: int + num_prompt_tokens = 0 # type: int + no_collect_tokens = False # type: bool + children = [] # type: List[WatchedSpan] + is_pipeline = False # type: bool + + def __init__(self, span): + # type: (Span) -> None + self.span = span + + +class SentryLangchainCallback(BaseCallbackHandler): # type: ignore[misc] + """Base callback handler that can be used to handle callbacks from langchain.""" + + span_map = OrderedDict() # type: OrderedDict[UUID, WatchedSpan] + + max_span_map_size = 0 + + def __init__(self, max_span_map_size, include_prompts, tiktoken_encoding_name=None): + # type: (int, bool, Optional[str]) -> None + self.max_span_map_size = max_span_map_size + self.include_prompts = include_prompts + + self.tiktoken_encoding = None + if tiktoken_encoding_name is not None: + import tiktoken # type: ignore + + self.tiktoken_encoding = tiktoken.get_encoding(tiktoken_encoding_name) + + def count_tokens(self, s): + # type: (str) -> int + if self.tiktoken_encoding is not None: + return len(self.tiktoken_encoding.encode_ordinary(s)) + return 0 + + def gc_span_map(self): + # type: () -> None + + while len(self.span_map) > self.max_span_map_size: + run_id, watched_span = self.span_map.popitem(last=False) + self._exit_span(watched_span, run_id) + + def _handle_error(self, run_id, error): + # type: (UUID, Any) -> None + if not run_id or run_id not in self.span_map: + return + + span_data = self.span_map[run_id] + if not span_data: + return + sentry_sdk.capture_exception(error, span_data.span.scope) + span_data.span.__exit__(None, None, None) + del self.span_map[run_id] + + def _normalize_langchain_message(self, message): + # type: (BaseMessage) -> Any + parsed = {"content": message.content, "role": message.type} + parsed.update(message.additional_kwargs) + return parsed + + def _create_span(self, run_id, parent_id, **kwargs): + # type: (SentryLangchainCallback, UUID, Optional[Any], Any) -> WatchedSpan + + watched_span = None # type: Optional[WatchedSpan] + if parent_id: + parent_span = self.span_map.get(parent_id) # type: Optional[WatchedSpan] + if parent_span: + watched_span = WatchedSpan(parent_span.span.start_child(**kwargs)) + parent_span.children.append(watched_span) + if watched_span is None: + watched_span = WatchedSpan(sentry_sdk.start_span(**kwargs)) + + if kwargs.get("op", "").startswith("ai.pipeline."): + if kwargs.get("name"): + set_ai_pipeline_name(kwargs.get("name")) + watched_span.is_pipeline = True + + watched_span.span.__enter__() + self.span_map[run_id] = watched_span + self.gc_span_map() + return watched_span + + def _exit_span(self, span_data, run_id): + # type: (SentryLangchainCallback, WatchedSpan, UUID) -> None + + if span_data.is_pipeline: + set_ai_pipeline_name(None) + + span_data.span.__exit__(None, None, None) + del self.span_map[run_id] + + def on_llm_start( + self, + serialized, + prompts, + *, + run_id, + tags=None, + parent_run_id=None, + metadata=None, + **kwargs, + ): + # type: (SentryLangchainCallback, Dict[str, Any], List[str], UUID, Optional[List[str]], Optional[UUID], Optional[Dict[str, Any]], Any) -> Any + """Run when LLM starts running.""" + with capture_internal_exceptions(): + if not run_id: + return + all_params = kwargs.get("invocation_params", {}) + all_params.update(serialized.get("kwargs", {})) + watched_span = self._create_span( + run_id, + kwargs.get("parent_run_id"), + op=OP.LANGCHAIN_RUN, + name=kwargs.get("name") or "Langchain LLM call", + origin=LangchainIntegration.origin, + ) + span = watched_span.span + if should_send_default_pii() and self.include_prompts: + set_data_normalized(span, SPANDATA.AI_INPUT_MESSAGES, prompts) + for k, v in DATA_FIELDS.items(): + if k in all_params: + set_data_normalized(span, v, all_params[k]) + + def on_chat_model_start(self, serialized, messages, *, run_id, **kwargs): + # type: (SentryLangchainCallback, Dict[str, Any], List[List[BaseMessage]], UUID, Any) -> Any + """Run when Chat Model starts running.""" + with capture_internal_exceptions(): + if not run_id: + return + all_params = kwargs.get("invocation_params", {}) + all_params.update(serialized.get("kwargs", {})) + watched_span = self._create_span( + run_id, + kwargs.get("parent_run_id"), + op=OP.LANGCHAIN_CHAT_COMPLETIONS_CREATE, + name=kwargs.get("name") or "Langchain Chat Model", + origin=LangchainIntegration.origin, + ) + span = watched_span.span + model = all_params.get( + "model", all_params.get("model_name", all_params.get("model_id")) + ) + watched_span.no_collect_tokens = any( + x in all_params.get("_type", "") for x in NO_COLLECT_TOKEN_MODELS + ) + + if not model and "anthropic" in all_params.get("_type"): + model = "claude-2" + if model: + span.set_data(SPANDATA.AI_MODEL_ID, model) + if should_send_default_pii() and self.include_prompts: + set_data_normalized( + span, + SPANDATA.AI_INPUT_MESSAGES, + [ + [self._normalize_langchain_message(x) for x in list_] + for list_ in messages + ], + ) + for k, v in DATA_FIELDS.items(): + if k in all_params: + set_data_normalized(span, v, all_params[k]) + if not watched_span.no_collect_tokens: + for list_ in messages: + for message in list_: + self.span_map[run_id].num_prompt_tokens += self.count_tokens( + message.content + ) + self.count_tokens(message.type) + + def on_llm_new_token(self, token, *, run_id, **kwargs): + # type: (SentryLangchainCallback, str, UUID, Any) -> Any + """Run on new LLM token. Only available when streaming is enabled.""" + with capture_internal_exceptions(): + if not run_id or run_id not in self.span_map: + return + span_data = self.span_map[run_id] + if not span_data or span_data.no_collect_tokens: + return + span_data.num_completion_tokens += self.count_tokens(token) + + def on_llm_end(self, response, *, run_id, **kwargs): + # type: (SentryLangchainCallback, LLMResult, UUID, Any) -> Any + """Run when LLM ends running.""" + with capture_internal_exceptions(): + if not run_id: + return + + token_usage = ( + response.llm_output.get("token_usage") if response.llm_output else None + ) + + span_data = self.span_map[run_id] + if not span_data: + return + + if should_send_default_pii() and self.include_prompts: + set_data_normalized( + span_data.span, + SPANDATA.AI_RESPONSES, + [[x.text for x in list_] for list_ in response.generations], + ) + + if not span_data.no_collect_tokens: + if token_usage: + record_token_usage( + span_data.span, + token_usage.get("prompt_tokens"), + token_usage.get("completion_tokens"), + token_usage.get("total_tokens"), + ) + else: + record_token_usage( + span_data.span, + span_data.num_prompt_tokens, + span_data.num_completion_tokens, + ) + + self._exit_span(span_data, run_id) + + def on_llm_error(self, error, *, run_id, **kwargs): + # type: (SentryLangchainCallback, Union[Exception, KeyboardInterrupt], UUID, Any) -> Any + """Run when LLM errors.""" + with capture_internal_exceptions(): + self._handle_error(run_id, error) + + def on_chain_start(self, serialized, inputs, *, run_id, **kwargs): + # type: (SentryLangchainCallback, Dict[str, Any], Dict[str, Any], UUID, Any) -> Any + """Run when chain starts running.""" + with capture_internal_exceptions(): + if not run_id: + return + watched_span = self._create_span( + run_id, + kwargs.get("parent_run_id"), + op=( + OP.LANGCHAIN_RUN + if kwargs.get("parent_run_id") is not None + else OP.LANGCHAIN_PIPELINE + ), + name=kwargs.get("name") or "Chain execution", + origin=LangchainIntegration.origin, + ) + metadata = kwargs.get("metadata") + if metadata: + set_data_normalized(watched_span.span, SPANDATA.AI_METADATA, metadata) + + def on_chain_end(self, outputs, *, run_id, **kwargs): + # type: (SentryLangchainCallback, Dict[str, Any], UUID, Any) -> Any + """Run when chain ends running.""" + with capture_internal_exceptions(): + if not run_id or run_id not in self.span_map: + return + + span_data = self.span_map[run_id] + if not span_data: + return + self._exit_span(span_data, run_id) + + def on_chain_error(self, error, *, run_id, **kwargs): + # type: (SentryLangchainCallback, Union[Exception, KeyboardInterrupt], UUID, Any) -> Any + """Run when chain errors.""" + self._handle_error(run_id, error) + + def on_agent_action(self, action, *, run_id, **kwargs): + # type: (SentryLangchainCallback, AgentAction, UUID, Any) -> Any + with capture_internal_exceptions(): + if not run_id: + return + watched_span = self._create_span( + run_id, + kwargs.get("parent_run_id"), + op=OP.LANGCHAIN_AGENT, + name=action.tool or "AI tool usage", + origin=LangchainIntegration.origin, + ) + if action.tool_input and should_send_default_pii() and self.include_prompts: + set_data_normalized( + watched_span.span, SPANDATA.AI_INPUT_MESSAGES, action.tool_input + ) + + def on_agent_finish(self, finish, *, run_id, **kwargs): + # type: (SentryLangchainCallback, AgentFinish, UUID, Any) -> Any + with capture_internal_exceptions(): + if not run_id: + return + + span_data = self.span_map[run_id] + if not span_data: + return + if should_send_default_pii() and self.include_prompts: + set_data_normalized( + span_data.span, SPANDATA.AI_RESPONSES, finish.return_values.items() + ) + self._exit_span(span_data, run_id) + + def on_tool_start(self, serialized, input_str, *, run_id, **kwargs): + # type: (SentryLangchainCallback, Dict[str, Any], str, UUID, Any) -> Any + """Run when tool starts running.""" + with capture_internal_exceptions(): + if not run_id: + return + watched_span = self._create_span( + run_id, + kwargs.get("parent_run_id"), + op=OP.LANGCHAIN_TOOL, + name=serialized.get("name") or kwargs.get("name") or "AI tool usage", + origin=LangchainIntegration.origin, + ) + if should_send_default_pii() and self.include_prompts: + set_data_normalized( + watched_span.span, + SPANDATA.AI_INPUT_MESSAGES, + kwargs.get("inputs", [input_str]), + ) + if kwargs.get("metadata"): + set_data_normalized( + watched_span.span, SPANDATA.AI_METADATA, kwargs.get("metadata") + ) + + def on_tool_end(self, output, *, run_id, **kwargs): + # type: (SentryLangchainCallback, str, UUID, Any) -> Any + """Run when tool ends running.""" + with capture_internal_exceptions(): + if not run_id or run_id not in self.span_map: + return + + span_data = self.span_map[run_id] + if not span_data: + return + if should_send_default_pii() and self.include_prompts: + set_data_normalized(span_data.span, SPANDATA.AI_RESPONSES, output) + self._exit_span(span_data, run_id) + + def on_tool_error(self, error, *args, run_id, **kwargs): + # type: (SentryLangchainCallback, Union[Exception, KeyboardInterrupt], UUID, Any) -> Any + """Run when tool errors.""" + self._handle_error(run_id, error) + + +def _wrap_configure(f): + # type: (Callable[..., Any]) -> Callable[..., Any] + + @wraps(f) + def new_configure(*args, **kwargs): + # type: (Any, Any) -> Any + + integration = sentry_sdk.get_client().get_integration(LangchainIntegration) + if integration is None: + return f(*args, **kwargs) + + with capture_internal_exceptions(): + new_callbacks = [] # type: List[BaseCallbackHandler] + if "local_callbacks" in kwargs: + existing_callbacks = kwargs["local_callbacks"] + kwargs["local_callbacks"] = new_callbacks + elif len(args) > 2: + existing_callbacks = args[2] + args = ( + args[0], + args[1], + new_callbacks, + ) + args[3:] + else: + existing_callbacks = [] + + if existing_callbacks: + if isinstance(existing_callbacks, list): + for cb in existing_callbacks: + new_callbacks.append(cb) + elif isinstance(existing_callbacks, BaseCallbackHandler): + new_callbacks.append(existing_callbacks) + else: + logger.debug("Unknown callback type: %s", existing_callbacks) + + already_added = False + for callback in new_callbacks: + if isinstance(callback, SentryLangchainCallback): + already_added = True + + if not already_added: + new_callbacks.append( + SentryLangchainCallback( + integration.max_spans, + integration.include_prompts, + integration.tiktoken_encoding_name, + ) + ) + return f(*args, **kwargs) + + return new_configure diff --git a/aws/lambda_demo/sentry_sdk/integrations/launchdarkly.py b/aws/lambda_demo/sentry_sdk/integrations/launchdarkly.py new file mode 100644 index 000000000..cb9e91146 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/launchdarkly.py @@ -0,0 +1,62 @@ +from typing import TYPE_CHECKING +import sentry_sdk + +from sentry_sdk.integrations import DidNotEnable, Integration + +try: + import ldclient + from ldclient.hook import Hook, Metadata + + if TYPE_CHECKING: + from ldclient import LDClient + from ldclient.hook import EvaluationSeriesContext + from ldclient.evaluation import EvaluationDetail + + from typing import Any +except ImportError: + raise DidNotEnable("LaunchDarkly is not installed") + + +class LaunchDarklyIntegration(Integration): + identifier = "launchdarkly" + + def __init__(self, ld_client=None): + # type: (LDClient | None) -> None + """ + :param client: An initialized LDClient instance. If a client is not provided, this + integration will attempt to use the shared global instance. + """ + try: + client = ld_client or ldclient.get() + except Exception as exc: + raise DidNotEnable("Error getting LaunchDarkly client. " + repr(exc)) + + if not client.is_initialized(): + raise DidNotEnable("LaunchDarkly client is not initialized.") + + # Register the flag collection hook with the LD client. + client.add_hook(LaunchDarklyHook()) + + @staticmethod + def setup_once(): + # type: () -> None + pass + + +class LaunchDarklyHook(Hook): + + @property + def metadata(self): + # type: () -> Metadata + return Metadata(name="sentry-flag-auditor") + + def after_evaluation(self, series_context, data, detail): + # type: (EvaluationSeriesContext, dict[Any, Any], EvaluationDetail) -> dict[Any, Any] + if isinstance(detail.value, bool): + flags = sentry_sdk.get_current_scope().flags + flags.set(series_context.key, detail.value) + return data + + def before_evaluation(self, series_context, data): + # type: (EvaluationSeriesContext, dict[Any, Any]) -> dict[Any, Any] + return data # No-op. diff --git a/aws/lambda_demo/sentry_sdk/integrations/litestar.py b/aws/lambda_demo/sentry_sdk/integrations/litestar.py new file mode 100644 index 000000000..4b04dada8 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/litestar.py @@ -0,0 +1,286 @@ +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.integrations.asgi import SentryAsgiMiddleware +from sentry_sdk.integrations.logging import ignore_logger +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import SOURCE_FOR_STYLE, TRANSACTION_SOURCE_ROUTE +from sentry_sdk.utils import ( + ensure_integration_enabled, + event_from_exception, + transaction_from_function, +) + +try: + from litestar import Request, Litestar # type: ignore + from litestar.handlers.base import BaseRouteHandler # type: ignore + from litestar.middleware import DefineMiddleware # type: ignore + from litestar.routes.http import HTTPRoute # type: ignore + from litestar.data_extractors import ConnectionDataExtractor # type: ignore +except ImportError: + raise DidNotEnable("Litestar is not installed") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Optional, Union + from litestar.types.asgi_types import ASGIApp # type: ignore + from litestar.types import ( # type: ignore + HTTPReceiveMessage, + HTTPScope, + Message, + Middleware, + Receive, + Scope as LitestarScope, + Send, + WebSocketReceiveMessage, + ) + from litestar.middleware import MiddlewareProtocol + from sentry_sdk._types import Event, Hint + +_DEFAULT_TRANSACTION_NAME = "generic Litestar request" + + +class LitestarIntegration(Integration): + identifier = "litestar" + origin = f"auto.http.{identifier}" + + @staticmethod + def setup_once(): + # type: () -> None + patch_app_init() + patch_middlewares() + patch_http_route_handle() + + # The following line follows the pattern found in other integrations such as `DjangoIntegration.setup_once`. + # The Litestar `ExceptionHandlerMiddleware.__call__` catches exceptions and does the following + # (among other things): + # 1. Logs them, some at least (such as 500s) as errors + # 2. Calls after_exception hooks + # The `LitestarIntegration`` provides an after_exception hook (see `patch_app_init` below) to create a Sentry event + # from an exception, which ends up being called during step 2 above. However, the Sentry `LoggingIntegration` will + # by default create a Sentry event from error logs made in step 1 if we do not prevent it from doing so. + ignore_logger("litestar") + + +class SentryLitestarASGIMiddleware(SentryAsgiMiddleware): + def __init__(self, app, span_origin=LitestarIntegration.origin): + # type: (ASGIApp, str) -> None + + super().__init__( + app=app, + unsafe_context_data=False, + transaction_style="endpoint", + mechanism_type="asgi", + span_origin=span_origin, + ) + + +def patch_app_init(): + # type: () -> None + """ + Replaces the Litestar class's `__init__` function in order to inject `after_exception` handlers and set the + `SentryLitestarASGIMiddleware` as the outmost middleware in the stack. + See: + - https://docs.litestar.dev/2/usage/applications.html#after-exception + - https://docs.litestar.dev/2/usage/middleware/using-middleware.html + """ + old__init__ = Litestar.__init__ + + @ensure_integration_enabled(LitestarIntegration, old__init__) + def injection_wrapper(self, *args, **kwargs): + # type: (Litestar, *Any, **Any) -> None + kwargs["after_exception"] = [ + exception_handler, + *(kwargs.get("after_exception") or []), + ] + + SentryLitestarASGIMiddleware.__call__ = SentryLitestarASGIMiddleware._run_asgi3 # type: ignore + middleware = kwargs.get("middleware") or [] + kwargs["middleware"] = [SentryLitestarASGIMiddleware, *middleware] + old__init__(self, *args, **kwargs) + + Litestar.__init__ = injection_wrapper + + +def patch_middlewares(): + # type: () -> None + old_resolve_middleware_stack = BaseRouteHandler.resolve_middleware + + @ensure_integration_enabled(LitestarIntegration, old_resolve_middleware_stack) + def resolve_middleware_wrapper(self): + # type: (BaseRouteHandler) -> list[Middleware] + return [ + enable_span_for_middleware(middleware) + for middleware in old_resolve_middleware_stack(self) + ] + + BaseRouteHandler.resolve_middleware = resolve_middleware_wrapper + + +def enable_span_for_middleware(middleware): + # type: (Middleware) -> Middleware + if ( + not hasattr(middleware, "__call__") # noqa: B004 + or middleware is SentryLitestarASGIMiddleware + ): + return middleware + + if isinstance(middleware, DefineMiddleware): + old_call = middleware.middleware.__call__ # type: ASGIApp + else: + old_call = middleware.__call__ + + async def _create_span_call(self, scope, receive, send): + # type: (MiddlewareProtocol, LitestarScope, Receive, Send) -> None + if sentry_sdk.get_client().get_integration(LitestarIntegration) is None: + return await old_call(self, scope, receive, send) + + middleware_name = self.__class__.__name__ + with sentry_sdk.start_span( + op=OP.MIDDLEWARE_LITESTAR, + name=middleware_name, + origin=LitestarIntegration.origin, + ) as middleware_span: + middleware_span.set_tag("litestar.middleware_name", middleware_name) + + # Creating spans for the "receive" callback + async def _sentry_receive(*args, **kwargs): + # type: (*Any, **Any) -> Union[HTTPReceiveMessage, WebSocketReceiveMessage] + if sentry_sdk.get_client().get_integration(LitestarIntegration) is None: + return await receive(*args, **kwargs) + with sentry_sdk.start_span( + op=OP.MIDDLEWARE_LITESTAR_RECEIVE, + name=getattr(receive, "__qualname__", str(receive)), + origin=LitestarIntegration.origin, + ) as span: + span.set_tag("litestar.middleware_name", middleware_name) + return await receive(*args, **kwargs) + + receive_name = getattr(receive, "__name__", str(receive)) + receive_patched = receive_name == "_sentry_receive" + new_receive = _sentry_receive if not receive_patched else receive + + # Creating spans for the "send" callback + async def _sentry_send(message): + # type: (Message) -> None + if sentry_sdk.get_client().get_integration(LitestarIntegration) is None: + return await send(message) + with sentry_sdk.start_span( + op=OP.MIDDLEWARE_LITESTAR_SEND, + name=getattr(send, "__qualname__", str(send)), + origin=LitestarIntegration.origin, + ) as span: + span.set_tag("litestar.middleware_name", middleware_name) + return await send(message) + + send_name = getattr(send, "__name__", str(send)) + send_patched = send_name == "_sentry_send" + new_send = _sentry_send if not send_patched else send + + return await old_call(self, scope, new_receive, new_send) + + not_yet_patched = old_call.__name__ not in ["_create_span_call"] + + if not_yet_patched: + if isinstance(middleware, DefineMiddleware): + middleware.middleware.__call__ = _create_span_call + else: + middleware.__call__ = _create_span_call + + return middleware + + +def patch_http_route_handle(): + # type: () -> None + old_handle = HTTPRoute.handle + + async def handle_wrapper(self, scope, receive, send): + # type: (HTTPRoute, HTTPScope, Receive, Send) -> None + if sentry_sdk.get_client().get_integration(LitestarIntegration) is None: + return await old_handle(self, scope, receive, send) + + sentry_scope = sentry_sdk.get_isolation_scope() + request = scope["app"].request_class( + scope=scope, receive=receive, send=send + ) # type: Request[Any, Any] + extracted_request_data = ConnectionDataExtractor( + parse_body=True, parse_query=True + )(request) + body = extracted_request_data.pop("body") + + request_data = await body + + def event_processor(event, _): + # type: (Event, Hint) -> Event + route_handler = scope.get("route_handler") + + request_info = event.get("request", {}) + request_info["content_length"] = len(scope.get("_body", b"")) + if should_send_default_pii(): + request_info["cookies"] = extracted_request_data["cookies"] + if request_data is not None: + request_info["data"] = request_data + + func = None + if route_handler.name is not None: + tx_name = route_handler.name + # Accounts for use of type `Ref` in earlier versions of litestar without the need to reference it as a type + elif hasattr(route_handler.fn, "value"): + func = route_handler.fn.value + else: + func = route_handler.fn + if func is not None: + tx_name = transaction_from_function(func) + + tx_info = {"source": SOURCE_FOR_STYLE["endpoint"]} + + if not tx_name: + tx_name = _DEFAULT_TRANSACTION_NAME + tx_info = {"source": TRANSACTION_SOURCE_ROUTE} + + event.update( + { + "request": request_info, + "transaction": tx_name, + "transaction_info": tx_info, + } + ) + return event + + sentry_scope._name = LitestarIntegration.identifier + sentry_scope.add_event_processor(event_processor) + + return await old_handle(self, scope, receive, send) + + HTTPRoute.handle = handle_wrapper + + +def retrieve_user_from_scope(scope): + # type: (LitestarScope) -> Optional[dict[str, Any]] + scope_user = scope.get("user") + if isinstance(scope_user, dict): + return scope_user + if hasattr(scope_user, "asdict"): # dataclasses + return scope_user.asdict() + + return None + + +@ensure_integration_enabled(LitestarIntegration) +def exception_handler(exc, scope): + # type: (Exception, LitestarScope) -> None + user_info = None # type: Optional[dict[str, Any]] + if should_send_default_pii(): + user_info = retrieve_user_from_scope(scope) + if user_info and isinstance(user_info, dict): + sentry_scope = sentry_sdk.get_isolation_scope() + sentry_scope.set_user(user_info) + + event, hint = event_from_exception( + exc, + client_options=sentry_sdk.get_client().options, + mechanism={"type": LitestarIntegration.identifier, "handled": False}, + ) + + sentry_sdk.capture_event(event, hint=hint) diff --git a/aws/lambda_demo/sentry_sdk/integrations/logging.py b/aws/lambda_demo/sentry_sdk/integrations/logging.py new file mode 100644 index 000000000..b792510d6 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/logging.py @@ -0,0 +1,294 @@ +import logging +from datetime import datetime, timezone +from fnmatch import fnmatch + +import sentry_sdk +from sentry_sdk.utils import ( + to_string, + event_from_exception, + current_stacktrace, + capture_internal_exceptions, +) +from sentry_sdk.integrations import Integration + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import MutableMapping + from logging import LogRecord + from typing import Any + from typing import Dict + from typing import Optional + +DEFAULT_LEVEL = logging.INFO +DEFAULT_EVENT_LEVEL = logging.ERROR +LOGGING_TO_EVENT_LEVEL = { + logging.NOTSET: "notset", + logging.DEBUG: "debug", + logging.INFO: "info", + logging.WARN: "warning", # WARN is same a WARNING + logging.WARNING: "warning", + logging.ERROR: "error", + logging.FATAL: "fatal", + logging.CRITICAL: "fatal", # CRITICAL is same as FATAL +} + +# Capturing events from those loggers causes recursion errors. We cannot allow +# the user to unconditionally create events from those loggers under any +# circumstances. +# +# Note: Ignoring by logger name here is better than mucking with thread-locals. +# We do not necessarily know whether thread-locals work 100% correctly in the user's environment. +_IGNORED_LOGGERS = set( + ["sentry_sdk.errors", "urllib3.connectionpool", "urllib3.connection"] +) + + +def ignore_logger( + name, # type: str +): + # type: (...) -> None + """This disables recording (both in breadcrumbs and as events) calls to + a logger of a specific name. Among other uses, many of our integrations + use this to prevent their actions being recorded as breadcrumbs. Exposed + to users as a way to quiet spammy loggers. + + :param name: The name of the logger to ignore (same string you would pass to ``logging.getLogger``). + """ + _IGNORED_LOGGERS.add(name) + + +class LoggingIntegration(Integration): + identifier = "logging" + + def __init__(self, level=DEFAULT_LEVEL, event_level=DEFAULT_EVENT_LEVEL): + # type: (Optional[int], Optional[int]) -> None + self._handler = None + self._breadcrumb_handler = None + + if level is not None: + self._breadcrumb_handler = BreadcrumbHandler(level=level) + + if event_level is not None: + self._handler = EventHandler(level=event_level) + + def _handle_record(self, record): + # type: (LogRecord) -> None + if self._handler is not None and record.levelno >= self._handler.level: + self._handler.handle(record) + + if ( + self._breadcrumb_handler is not None + and record.levelno >= self._breadcrumb_handler.level + ): + self._breadcrumb_handler.handle(record) + + @staticmethod + def setup_once(): + # type: () -> None + old_callhandlers = logging.Logger.callHandlers + + def sentry_patched_callhandlers(self, record): + # type: (Any, LogRecord) -> Any + # keeping a local reference because the + # global might be discarded on shutdown + ignored_loggers = _IGNORED_LOGGERS + + try: + return old_callhandlers(self, record) + finally: + # This check is done twice, once also here before we even get + # the integration. Otherwise we have a high chance of getting + # into a recursion error when the integration is resolved + # (this also is slower). + if ignored_loggers is not None and record.name not in ignored_loggers: + integration = sentry_sdk.get_client().get_integration( + LoggingIntegration + ) + if integration is not None: + integration._handle_record(record) + + logging.Logger.callHandlers = sentry_patched_callhandlers # type: ignore + + +class _BaseHandler(logging.Handler): + COMMON_RECORD_ATTRS = frozenset( + ( + "args", + "created", + "exc_info", + "exc_text", + "filename", + "funcName", + "levelname", + "levelno", + "linenno", + "lineno", + "message", + "module", + "msecs", + "msg", + "name", + "pathname", + "process", + "processName", + "relativeCreated", + "stack", + "tags", + "taskName", + "thread", + "threadName", + "stack_info", + ) + ) + + def _can_record(self, record): + # type: (LogRecord) -> bool + """Prevents ignored loggers from recording""" + for logger in _IGNORED_LOGGERS: + if fnmatch(record.name, logger): + return False + return True + + def _logging_to_event_level(self, record): + # type: (LogRecord) -> str + return LOGGING_TO_EVENT_LEVEL.get( + record.levelno, record.levelname.lower() if record.levelname else "" + ) + + def _extra_from_record(self, record): + # type: (LogRecord) -> MutableMapping[str, object] + return { + k: v + for k, v in vars(record).items() + if k not in self.COMMON_RECORD_ATTRS + and (not isinstance(k, str) or not k.startswith("_")) + } + + +class EventHandler(_BaseHandler): + """ + A logging handler that emits Sentry events for each log record + + Note that you do not have to use this class if the logging integration is enabled, which it is by default. + """ + + def emit(self, record): + # type: (LogRecord) -> Any + with capture_internal_exceptions(): + self.format(record) + return self._emit(record) + + def _emit(self, record): + # type: (LogRecord) -> None + if not self._can_record(record): + return + + client = sentry_sdk.get_client() + if not client.is_active(): + return + + client_options = client.options + + # exc_info might be None or (None, None, None) + # + # exc_info may also be any falsy value due to Python stdlib being + # liberal with what it receives and Celery's billiard being "liberal" + # with what it sends. See + # https://github.com/getsentry/sentry-python/issues/904 + if record.exc_info and record.exc_info[0] is not None: + event, hint = event_from_exception( + record.exc_info, + client_options=client_options, + mechanism={"type": "logging", "handled": True}, + ) + elif (record.exc_info and record.exc_info[0] is None) or record.stack_info: + event = {} + hint = {} + with capture_internal_exceptions(): + event["threads"] = { + "values": [ + { + "stacktrace": current_stacktrace( + include_local_variables=client_options[ + "include_local_variables" + ], + max_value_length=client_options["max_value_length"], + ), + "crashed": False, + "current": True, + } + ] + } + else: + event = {} + hint = {} + + hint["log_record"] = record + + level = self._logging_to_event_level(record) + if level in {"debug", "info", "warning", "error", "critical", "fatal"}: + event["level"] = level # type: ignore[typeddict-item] + event["logger"] = record.name + + # Log records from `warnings` module as separate issues + record_caputured_from_warnings_module = ( + record.name == "py.warnings" and record.msg == "%s" + ) + if record_caputured_from_warnings_module: + # use the actual message and not "%s" as the message + # this prevents grouping all warnings under one "%s" issue + msg = record.args[0] # type: ignore + + event["logentry"] = { + "message": msg, + "params": (), + } + + else: + event["logentry"] = { + "message": to_string(record.msg), + "params": record.args, + } + + event["extra"] = self._extra_from_record(record) + + sentry_sdk.capture_event(event, hint=hint) + + +# Legacy name +SentryHandler = EventHandler + + +class BreadcrumbHandler(_BaseHandler): + """ + A logging handler that records breadcrumbs for each log record. + + Note that you do not have to use this class if the logging integration is enabled, which it is by default. + """ + + def emit(self, record): + # type: (LogRecord) -> Any + with capture_internal_exceptions(): + self.format(record) + return self._emit(record) + + def _emit(self, record): + # type: (LogRecord) -> None + if not self._can_record(record): + return + + sentry_sdk.add_breadcrumb( + self._breadcrumb_from_record(record), hint={"log_record": record} + ) + + def _breadcrumb_from_record(self, record): + # type: (LogRecord) -> Dict[str, Any] + return { + "type": "log", + "level": self._logging_to_event_level(record), + "category": record.name, + "message": record.message, + "timestamp": datetime.fromtimestamp(record.created, timezone.utc), + "data": self._extra_from_record(record), + } diff --git a/aws/lambda_demo/sentry_sdk/integrations/loguru.py b/aws/lambda_demo/sentry_sdk/integrations/loguru.py new file mode 100644 index 000000000..da99dfc4d --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/loguru.py @@ -0,0 +1,100 @@ +import enum + +from sentry_sdk.integrations import Integration, DidNotEnable +from sentry_sdk.integrations.logging import ( + BreadcrumbHandler, + EventHandler, + _BaseHandler, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from logging import LogRecord + from typing import Optional, Tuple + +try: + import loguru + from loguru import logger + from loguru._defaults import LOGURU_FORMAT as DEFAULT_FORMAT +except ImportError: + raise DidNotEnable("LOGURU is not installed") + + +class LoggingLevels(enum.IntEnum): + TRACE = 5 + DEBUG = 10 + INFO = 20 + SUCCESS = 25 + WARNING = 30 + ERROR = 40 + CRITICAL = 50 + + +DEFAULT_LEVEL = LoggingLevels.INFO.value +DEFAULT_EVENT_LEVEL = LoggingLevels.ERROR.value +# We need to save the handlers to be able to remove them later +# in tests (they call `LoguruIntegration.__init__` multiple times, +# and we can't use `setup_once` because it's called before +# than we get configuration). +_ADDED_HANDLERS = (None, None) # type: Tuple[Optional[int], Optional[int]] + + +class LoguruIntegration(Integration): + identifier = "loguru" + + def __init__( + self, + level=DEFAULT_LEVEL, + event_level=DEFAULT_EVENT_LEVEL, + breadcrumb_format=DEFAULT_FORMAT, + event_format=DEFAULT_FORMAT, + ): + # type: (Optional[int], Optional[int], str | loguru.FormatFunction, str | loguru.FormatFunction) -> None + global _ADDED_HANDLERS + breadcrumb_handler, event_handler = _ADDED_HANDLERS + + if breadcrumb_handler is not None: + logger.remove(breadcrumb_handler) + breadcrumb_handler = None + if event_handler is not None: + logger.remove(event_handler) + event_handler = None + + if level is not None: + breadcrumb_handler = logger.add( + LoguruBreadcrumbHandler(level=level), + level=level, + format=breadcrumb_format, + ) + + if event_level is not None: + event_handler = logger.add( + LoguruEventHandler(level=event_level), + level=event_level, + format=event_format, + ) + + _ADDED_HANDLERS = (breadcrumb_handler, event_handler) + + @staticmethod + def setup_once(): + # type: () -> None + pass # we do everything in __init__ + + +class _LoguruBaseHandler(_BaseHandler): + def _logging_to_event_level(self, record): + # type: (LogRecord) -> str + try: + return LoggingLevels(record.levelno).name.lower() + except ValueError: + return record.levelname.lower() if record.levelname else "" + + +class LoguruEventHandler(_LoguruBaseHandler, EventHandler): + """Modified version of :class:`sentry_sdk.integrations.logging.EventHandler` to use loguru's level names.""" + + +class LoguruBreadcrumbHandler(_LoguruBaseHandler, BreadcrumbHandler): + """Modified version of :class:`sentry_sdk.integrations.logging.BreadcrumbHandler` to use loguru's level names.""" diff --git a/aws/lambda_demo/sentry_sdk/integrations/modules.py b/aws/lambda_demo/sentry_sdk/integrations/modules.py new file mode 100644 index 000000000..ce3ee7866 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/modules.py @@ -0,0 +1,29 @@ +import sentry_sdk +from sentry_sdk.integrations import Integration +from sentry_sdk.scope import add_global_event_processor +from sentry_sdk.utils import _get_installed_modules + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from sentry_sdk._types import Event + + +class ModulesIntegration(Integration): + identifier = "modules" + + @staticmethod + def setup_once(): + # type: () -> None + @add_global_event_processor + def processor(event, hint): + # type: (Event, Any) -> Event + if event.get("type") == "transaction": + return event + + if sentry_sdk.get_client().get_integration(ModulesIntegration) is None: + return event + + event["modules"] = _get_installed_modules() + return event diff --git a/aws/lambda_demo/sentry_sdk/integrations/openai.py b/aws/lambda_demo/sentry_sdk/integrations/openai.py new file mode 100644 index 000000000..61d335b17 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/openai.py @@ -0,0 +1,429 @@ +from functools import wraps + +import sentry_sdk +from sentry_sdk import consts +from sentry_sdk.ai.monitoring import record_token_usage +from sentry_sdk.ai.utils import set_data_normalized +from sentry_sdk.consts import SPANDATA +from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.utils import ( + capture_internal_exceptions, + event_from_exception, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Iterable, List, Optional, Callable, AsyncIterator, Iterator + from sentry_sdk.tracing import Span + +try: + from openai.resources.chat.completions import Completions, AsyncCompletions + from openai.resources import Embeddings, AsyncEmbeddings + + if TYPE_CHECKING: + from openai.types.chat import ChatCompletionMessageParam, ChatCompletionChunk +except ImportError: + raise DidNotEnable("OpenAI not installed") + + +class OpenAIIntegration(Integration): + identifier = "openai" + origin = f"auto.ai.{identifier}" + + def __init__(self, include_prompts=True, tiktoken_encoding_name=None): + # type: (OpenAIIntegration, bool, Optional[str]) -> None + self.include_prompts = include_prompts + + self.tiktoken_encoding = None + if tiktoken_encoding_name is not None: + import tiktoken # type: ignore + + self.tiktoken_encoding = tiktoken.get_encoding(tiktoken_encoding_name) + + @staticmethod + def setup_once(): + # type: () -> None + Completions.create = _wrap_chat_completion_create(Completions.create) + Embeddings.create = _wrap_embeddings_create(Embeddings.create) + + AsyncCompletions.create = _wrap_async_chat_completion_create( + AsyncCompletions.create + ) + AsyncEmbeddings.create = _wrap_async_embeddings_create(AsyncEmbeddings.create) + + def count_tokens(self, s): + # type: (OpenAIIntegration, str) -> int + if self.tiktoken_encoding is not None: + return len(self.tiktoken_encoding.encode_ordinary(s)) + return 0 + + +def _capture_exception(exc): + # type: (Any) -> None + event, hint = event_from_exception( + exc, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "openai", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + +def _calculate_chat_completion_usage( + messages, response, span, streaming_message_responses, count_tokens +): + # type: (Iterable[ChatCompletionMessageParam], Any, Span, Optional[List[str]], Callable[..., Any]) -> None + completion_tokens = 0 # type: Optional[int] + prompt_tokens = 0 # type: Optional[int] + total_tokens = 0 # type: Optional[int] + if hasattr(response, "usage"): + if hasattr(response.usage, "completion_tokens") and isinstance( + response.usage.completion_tokens, int + ): + completion_tokens = response.usage.completion_tokens + if hasattr(response.usage, "prompt_tokens") and isinstance( + response.usage.prompt_tokens, int + ): + prompt_tokens = response.usage.prompt_tokens + if hasattr(response.usage, "total_tokens") and isinstance( + response.usage.total_tokens, int + ): + total_tokens = response.usage.total_tokens + + if prompt_tokens == 0: + for message in messages: + if "content" in message: + prompt_tokens += count_tokens(message["content"]) + + if completion_tokens == 0: + if streaming_message_responses is not None: + for message in streaming_message_responses: + completion_tokens += count_tokens(message) + elif hasattr(response, "choices"): + for choice in response.choices: + if hasattr(choice, "message"): + completion_tokens += count_tokens(choice.message) + + if prompt_tokens == 0: + prompt_tokens = None + if completion_tokens == 0: + completion_tokens = None + if total_tokens == 0: + total_tokens = None + record_token_usage(span, prompt_tokens, completion_tokens, total_tokens) + + +def _new_chat_completion_common(f, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) + if integration is None: + return f(*args, **kwargs) + + if "messages" not in kwargs: + # invalid call (in all versions of openai), let it return error + return f(*args, **kwargs) + + try: + iter(kwargs["messages"]) + except TypeError: + # invalid call (in all versions), messages must be iterable + return f(*args, **kwargs) + + kwargs["messages"] = list(kwargs["messages"]) + messages = kwargs["messages"] + model = kwargs.get("model") + streaming = kwargs.get("stream") + + span = sentry_sdk.start_span( + op=consts.OP.OPENAI_CHAT_COMPLETIONS_CREATE, + name="Chat Completion", + origin=OpenAIIntegration.origin, + ) + span.__enter__() + + res = yield f, args, kwargs + + with capture_internal_exceptions(): + if should_send_default_pii() and integration.include_prompts: + set_data_normalized(span, SPANDATA.AI_INPUT_MESSAGES, messages) + + set_data_normalized(span, SPANDATA.AI_MODEL_ID, model) + set_data_normalized(span, SPANDATA.AI_STREAMING, streaming) + + if hasattr(res, "choices"): + if should_send_default_pii() and integration.include_prompts: + set_data_normalized( + span, + "ai.responses", + list(map(lambda x: x.message, res.choices)), + ) + _calculate_chat_completion_usage( + messages, res, span, None, integration.count_tokens + ) + span.__exit__(None, None, None) + elif hasattr(res, "_iterator"): + data_buf: list[list[str]] = [] # one for each choice + + old_iterator = res._iterator + + def new_iterator(): + # type: () -> Iterator[ChatCompletionChunk] + with capture_internal_exceptions(): + for x in old_iterator: + if hasattr(x, "choices"): + choice_index = 0 + for choice in x.choices: + if hasattr(choice, "delta") and hasattr( + choice.delta, "content" + ): + content = choice.delta.content + if len(data_buf) <= choice_index: + data_buf.append([]) + data_buf[choice_index].append(content or "") + choice_index += 1 + yield x + if len(data_buf) > 0: + all_responses = list( + map(lambda chunk: "".join(chunk), data_buf) + ) + if should_send_default_pii() and integration.include_prompts: + set_data_normalized( + span, SPANDATA.AI_RESPONSES, all_responses + ) + _calculate_chat_completion_usage( + messages, + res, + span, + all_responses, + integration.count_tokens, + ) + span.__exit__(None, None, None) + + async def new_iterator_async(): + # type: () -> AsyncIterator[ChatCompletionChunk] + with capture_internal_exceptions(): + async for x in old_iterator: + if hasattr(x, "choices"): + choice_index = 0 + for choice in x.choices: + if hasattr(choice, "delta") and hasattr( + choice.delta, "content" + ): + content = choice.delta.content + if len(data_buf) <= choice_index: + data_buf.append([]) + data_buf[choice_index].append(content or "") + choice_index += 1 + yield x + if len(data_buf) > 0: + all_responses = list( + map(lambda chunk: "".join(chunk), data_buf) + ) + if should_send_default_pii() and integration.include_prompts: + set_data_normalized( + span, SPANDATA.AI_RESPONSES, all_responses + ) + _calculate_chat_completion_usage( + messages, + res, + span, + all_responses, + integration.count_tokens, + ) + span.__exit__(None, None, None) + + if str(type(res._iterator)) == "": + res._iterator = new_iterator_async() + else: + res._iterator = new_iterator() + + else: + set_data_normalized(span, "unknown_response", True) + span.__exit__(None, None, None) + return res + + +def _wrap_chat_completion_create(f): + # type: (Callable[..., Any]) -> Callable[..., Any] + def _execute_sync(f, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + gen = _new_chat_completion_common(f, *args, **kwargs) + + try: + f, args, kwargs = next(gen) + except StopIteration as e: + return e.value + + try: + try: + result = f(*args, **kwargs) + except Exception as e: + _capture_exception(e) + raise e from None + + return gen.send(result) + except StopIteration as e: + return e.value + + @wraps(f) + def _sentry_patched_create_sync(*args, **kwargs): + # type: (*Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) + if integration is None or "messages" not in kwargs: + # no "messages" means invalid call (in all versions of openai), let it return error + return f(*args, **kwargs) + + return _execute_sync(f, *args, **kwargs) + + return _sentry_patched_create_sync + + +def _wrap_async_chat_completion_create(f): + # type: (Callable[..., Any]) -> Callable[..., Any] + async def _execute_async(f, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + gen = _new_chat_completion_common(f, *args, **kwargs) + + try: + f, args, kwargs = next(gen) + except StopIteration as e: + return await e.value + + try: + try: + result = await f(*args, **kwargs) + except Exception as e: + _capture_exception(e) + raise e from None + + return gen.send(result) + except StopIteration as e: + return e.value + + @wraps(f) + async def _sentry_patched_create_async(*args, **kwargs): + # type: (*Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) + if integration is None or "messages" not in kwargs: + # no "messages" means invalid call (in all versions of openai), let it return error + return await f(*args, **kwargs) + + return await _execute_async(f, *args, **kwargs) + + return _sentry_patched_create_async + + +def _new_embeddings_create_common(f, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) + if integration is None: + return f(*args, **kwargs) + + with sentry_sdk.start_span( + op=consts.OP.OPENAI_EMBEDDINGS_CREATE, + description="OpenAI Embedding Creation", + origin=OpenAIIntegration.origin, + ) as span: + if "input" in kwargs and ( + should_send_default_pii() and integration.include_prompts + ): + if isinstance(kwargs["input"], str): + set_data_normalized(span, "ai.input_messages", [kwargs["input"]]) + elif ( + isinstance(kwargs["input"], list) + and len(kwargs["input"]) > 0 + and isinstance(kwargs["input"][0], str) + ): + set_data_normalized(span, "ai.input_messages", kwargs["input"]) + if "model" in kwargs: + set_data_normalized(span, "ai.model_id", kwargs["model"]) + + response = yield f, args, kwargs + + prompt_tokens = 0 + total_tokens = 0 + if hasattr(response, "usage"): + if hasattr(response.usage, "prompt_tokens") and isinstance( + response.usage.prompt_tokens, int + ): + prompt_tokens = response.usage.prompt_tokens + if hasattr(response.usage, "total_tokens") and isinstance( + response.usage.total_tokens, int + ): + total_tokens = response.usage.total_tokens + + if prompt_tokens == 0: + prompt_tokens = integration.count_tokens(kwargs["input"] or "") + + record_token_usage(span, prompt_tokens, None, total_tokens or prompt_tokens) + + return response + + +def _wrap_embeddings_create(f): + # type: (Any) -> Any + def _execute_sync(f, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + gen = _new_embeddings_create_common(f, *args, **kwargs) + + try: + f, args, kwargs = next(gen) + except StopIteration as e: + return e.value + + try: + try: + result = f(*args, **kwargs) + except Exception as e: + _capture_exception(e) + raise e from None + + return gen.send(result) + except StopIteration as e: + return e.value + + @wraps(f) + def _sentry_patched_create_sync(*args, **kwargs): + # type: (*Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) + if integration is None: + return f(*args, **kwargs) + + return _execute_sync(f, *args, **kwargs) + + return _sentry_patched_create_sync + + +def _wrap_async_embeddings_create(f): + # type: (Any) -> Any + async def _execute_async(f, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + gen = _new_embeddings_create_common(f, *args, **kwargs) + + try: + f, args, kwargs = next(gen) + except StopIteration as e: + return await e.value + + try: + try: + result = await f(*args, **kwargs) + except Exception as e: + _capture_exception(e) + raise e from None + + return gen.send(result) + except StopIteration as e: + return e.value + + @wraps(f) + async def _sentry_patched_create_async(*args, **kwargs): + # type: (*Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) + if integration is None: + return await f(*args, **kwargs) + + return await _execute_async(f, *args, **kwargs) + + return _sentry_patched_create_async diff --git a/aws/lambda_demo/sentry_sdk/integrations/openfeature.py b/aws/lambda_demo/sentry_sdk/integrations/openfeature.py new file mode 100644 index 000000000..bf66b94e8 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/openfeature.py @@ -0,0 +1,39 @@ +from typing import TYPE_CHECKING +import sentry_sdk + +from sentry_sdk.integrations import DidNotEnable, Integration + +try: + from openfeature import api + from openfeature.hook import Hook + + if TYPE_CHECKING: + from openfeature.flag_evaluation import FlagEvaluationDetails + from openfeature.hook import HookContext, HookHints +except ImportError: + raise DidNotEnable("OpenFeature is not installed") + + +class OpenFeatureIntegration(Integration): + identifier = "openfeature" + + @staticmethod + def setup_once(): + # type: () -> None + # Register the hook within the global openfeature hooks list. + api.add_hooks(hooks=[OpenFeatureHook()]) + + +class OpenFeatureHook(Hook): + + def after(self, hook_context, details, hints): + # type: (HookContext, FlagEvaluationDetails[bool], HookHints) -> None + if isinstance(details.value, bool): + flags = sentry_sdk.get_current_scope().flags + flags.set(details.flag_key, details.value) + + def error(self, hook_context, exception, hints): + # type: (HookContext, Exception, HookHints) -> None + if isinstance(hook_context.default_value, bool): + flags = sentry_sdk.get_current_scope().flags + flags.set(hook_context.flag_key, hook_context.default_value) diff --git a/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/__init__.py b/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/__init__.py new file mode 100644 index 000000000..3c4c1a683 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/__init__.py @@ -0,0 +1,7 @@ +from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor +from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator + +__all__ = [ + "SentryPropagator", + "SentrySpanProcessor", +] diff --git a/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/consts.py b/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/consts.py new file mode 100644 index 000000000..ec493449d --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/consts.py @@ -0,0 +1,5 @@ +from opentelemetry.context import create_key + + +SENTRY_TRACE_KEY = create_key("sentry-trace") +SENTRY_BAGGAGE_KEY = create_key("sentry-baggage") diff --git a/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/integration.py b/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/integration.py new file mode 100644 index 000000000..43e0396c1 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/integration.py @@ -0,0 +1,58 @@ +""" +IMPORTANT: The contents of this file are part of a proof of concept and as such +are experimental and not suitable for production use. They may be changed or +removed at any time without prior notice. +""" + +from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator +from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor +from sentry_sdk.utils import logger + +try: + from opentelemetry import trace + from opentelemetry.propagate import set_global_textmap + from opentelemetry.sdk.trace import TracerProvider +except ImportError: + raise DidNotEnable("opentelemetry not installed") + +try: + from opentelemetry.instrumentation.django import DjangoInstrumentor # type: ignore[import-not-found] +except ImportError: + DjangoInstrumentor = None + + +CONFIGURABLE_INSTRUMENTATIONS = { + DjangoInstrumentor: {"is_sql_commentor_enabled": True}, +} + + +class OpenTelemetryIntegration(Integration): + identifier = "opentelemetry" + + @staticmethod + def setup_once(): + # type: () -> None + logger.warning( + "[OTel] Initializing highly experimental OpenTelemetry support. " + "Use at your own risk." + ) + + _setup_sentry_tracing() + # _setup_instrumentors() + + logger.debug("[OTel] Finished setting up OpenTelemetry integration") + + +def _setup_sentry_tracing(): + # type: () -> None + provider = TracerProvider() + provider.add_span_processor(SentrySpanProcessor()) + trace.set_tracer_provider(provider) + set_global_textmap(SentryPropagator()) + + +def _setup_instrumentors(): + # type: () -> None + for instrumentor, kwargs in CONFIGURABLE_INSTRUMENTATIONS.items(): + instrumentor().instrument(**kwargs) diff --git a/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/propagator.py b/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/propagator.py new file mode 100644 index 000000000..b84d582d6 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/propagator.py @@ -0,0 +1,117 @@ +from opentelemetry import trace +from opentelemetry.context import ( + Context, + get_current, + set_value, +) +from opentelemetry.propagators.textmap import ( + CarrierT, + Getter, + Setter, + TextMapPropagator, + default_getter, + default_setter, +) +from opentelemetry.trace import ( + NonRecordingSpan, + SpanContext, + TraceFlags, +) + +from sentry_sdk.integrations.opentelemetry.consts import ( + SENTRY_BAGGAGE_KEY, + SENTRY_TRACE_KEY, +) +from sentry_sdk.integrations.opentelemetry.span_processor import ( + SentrySpanProcessor, +) +from sentry_sdk.tracing import ( + BAGGAGE_HEADER_NAME, + SENTRY_TRACE_HEADER_NAME, +) +from sentry_sdk.tracing_utils import Baggage, extract_sentrytrace_data + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Optional, Set + + +class SentryPropagator(TextMapPropagator): + """ + Propagates tracing headers for Sentry's tracing system in a way OTel understands. + """ + + def extract(self, carrier, context=None, getter=default_getter): + # type: (CarrierT, Optional[Context], Getter[CarrierT]) -> Context + if context is None: + context = get_current() + + sentry_trace = getter.get(carrier, SENTRY_TRACE_HEADER_NAME) + if not sentry_trace: + return context + + sentrytrace = extract_sentrytrace_data(sentry_trace[0]) + if not sentrytrace: + return context + + context = set_value(SENTRY_TRACE_KEY, sentrytrace, context) + + trace_id, span_id = sentrytrace["trace_id"], sentrytrace["parent_span_id"] + + span_context = SpanContext( + trace_id=int(trace_id, 16), # type: ignore + span_id=int(span_id, 16), # type: ignore + # we simulate a sampled trace on the otel side and leave the sampling to sentry + trace_flags=TraceFlags(TraceFlags.SAMPLED), + is_remote=True, + ) + + baggage_header = getter.get(carrier, BAGGAGE_HEADER_NAME) + + if baggage_header: + baggage = Baggage.from_incoming_header(baggage_header[0]) + else: + # If there's an incoming sentry-trace but no incoming baggage header, + # for instance in traces coming from older SDKs, + # baggage will be empty and frozen and won't be populated as head SDK. + baggage = Baggage(sentry_items={}) + + baggage.freeze() + context = set_value(SENTRY_BAGGAGE_KEY, baggage, context) + + span = NonRecordingSpan(span_context) + modified_context = trace.set_span_in_context(span, context) + return modified_context + + def inject(self, carrier, context=None, setter=default_setter): + # type: (CarrierT, Optional[Context], Setter[CarrierT]) -> None + if context is None: + context = get_current() + + current_span = trace.get_current_span(context) + current_span_context = current_span.get_span_context() + + if not current_span_context.is_valid: + return + + span_id = trace.format_span_id(current_span_context.span_id) + + span_map = SentrySpanProcessor().otel_span_map + sentry_span = span_map.get(span_id, None) + if not sentry_span: + return + + setter.set(carrier, SENTRY_TRACE_HEADER_NAME, sentry_span.to_traceparent()) + + if sentry_span.containing_transaction: + baggage = sentry_span.containing_transaction.get_baggage() + if baggage: + baggage_data = baggage.serialize() + if baggage_data: + setter.set(carrier, BAGGAGE_HEADER_NAME, baggage_data) + + @property + def fields(self): + # type: () -> Set[str] + return {SENTRY_TRACE_HEADER_NAME, BAGGAGE_HEADER_NAME} diff --git a/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/span_processor.py b/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/span_processor.py new file mode 100644 index 000000000..e00562a50 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/opentelemetry/span_processor.py @@ -0,0 +1,391 @@ +from datetime import datetime, timezone +from time import time +from typing import TYPE_CHECKING, cast + +from opentelemetry.context import get_value +from opentelemetry.sdk.trace import SpanProcessor, ReadableSpan as OTelSpan +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import ( + format_span_id, + format_trace_id, + get_current_span, + SpanKind, +) +from opentelemetry.trace.span import ( + INVALID_SPAN_ID, + INVALID_TRACE_ID, +) +from sentry_sdk import get_client, start_transaction +from sentry_sdk.consts import INSTRUMENTER, SPANSTATUS +from sentry_sdk.integrations.opentelemetry.consts import ( + SENTRY_BAGGAGE_KEY, + SENTRY_TRACE_KEY, +) +from sentry_sdk.scope import add_global_event_processor +from sentry_sdk.tracing import Transaction, Span as SentrySpan +from sentry_sdk.utils import Dsn + +from urllib3.util import parse_url as urlparse + +if TYPE_CHECKING: + from typing import Any, Optional, Union + from opentelemetry import context as context_api + from sentry_sdk._types import Event, Hint + +OPEN_TELEMETRY_CONTEXT = "otel" +SPAN_MAX_TIME_OPEN_MINUTES = 10 +SPAN_ORIGIN = "auto.otel" + + +def link_trace_context_to_error_event(event, otel_span_map): + # type: (Event, dict[str, Union[Transaction, SentrySpan]]) -> Event + client = get_client() + + if client.options["instrumenter"] != INSTRUMENTER.OTEL: + return event + + if hasattr(event, "type") and event["type"] == "transaction": + return event + + otel_span = get_current_span() + if not otel_span: + return event + + ctx = otel_span.get_span_context() + + if ctx.trace_id == INVALID_TRACE_ID or ctx.span_id == INVALID_SPAN_ID: + return event + + sentry_span = otel_span_map.get(format_span_id(ctx.span_id), None) + if not sentry_span: + return event + + contexts = event.setdefault("contexts", {}) + contexts.setdefault("trace", {}).update(sentry_span.get_trace_context()) + + return event + + +class SentrySpanProcessor(SpanProcessor): + """ + Converts OTel spans into Sentry spans so they can be sent to the Sentry backend. + """ + + # The mapping from otel span ids to sentry spans + otel_span_map = {} # type: dict[str, Union[Transaction, SentrySpan]] + + # The currently open spans. Elements will be discarded after SPAN_MAX_TIME_OPEN_MINUTES + open_spans = {} # type: dict[int, set[str]] + + def __new__(cls): + # type: () -> SentrySpanProcessor + if not hasattr(cls, "instance"): + cls.instance = super().__new__(cls) + + return cls.instance + + def __init__(self): + # type: () -> None + @add_global_event_processor + def global_event_processor(event, hint): + # type: (Event, Hint) -> Event + return link_trace_context_to_error_event(event, self.otel_span_map) + + def _prune_old_spans(self): + # type: (SentrySpanProcessor) -> None + """ + Prune spans that have been open for too long. + """ + current_time_minutes = int(time() / 60) + for span_start_minutes in list( + self.open_spans.keys() + ): # making a list because we change the dict + # prune empty open spans buckets + if self.open_spans[span_start_minutes] == set(): + self.open_spans.pop(span_start_minutes) + + # prune old buckets + elif current_time_minutes - span_start_minutes > SPAN_MAX_TIME_OPEN_MINUTES: + for span_id in self.open_spans.pop(span_start_minutes): + self.otel_span_map.pop(span_id, None) + + def on_start(self, otel_span, parent_context=None): + # type: (OTelSpan, Optional[context_api.Context]) -> None + client = get_client() + + if not client.dsn: + return + + try: + _ = Dsn(client.dsn) + except Exception: + return + + if client.options["instrumenter"] != INSTRUMENTER.OTEL: + return + + if not otel_span.get_span_context().is_valid: + return + + if self._is_sentry_span(otel_span): + return + + trace_data = self._get_trace_data(otel_span, parent_context) + + parent_span_id = trace_data["parent_span_id"] + sentry_parent_span = ( + self.otel_span_map.get(parent_span_id) if parent_span_id else None + ) + + start_timestamp = None + if otel_span.start_time is not None: + start_timestamp = datetime.fromtimestamp( + otel_span.start_time / 1e9, timezone.utc + ) # OTel spans have nanosecond precision + + sentry_span = None + if sentry_parent_span: + sentry_span = sentry_parent_span.start_child( + span_id=trace_data["span_id"], + name=otel_span.name, + start_timestamp=start_timestamp, + instrumenter=INSTRUMENTER.OTEL, + origin=SPAN_ORIGIN, + ) + else: + sentry_span = start_transaction( + name=otel_span.name, + span_id=trace_data["span_id"], + parent_span_id=parent_span_id, + trace_id=trace_data["trace_id"], + baggage=trace_data["baggage"], + start_timestamp=start_timestamp, + instrumenter=INSTRUMENTER.OTEL, + origin=SPAN_ORIGIN, + ) + + self.otel_span_map[trace_data["span_id"]] = sentry_span + + if otel_span.start_time is not None: + span_start_in_minutes = int( + otel_span.start_time / 1e9 / 60 + ) # OTel spans have nanosecond precision + self.open_spans.setdefault(span_start_in_minutes, set()).add( + trace_data["span_id"] + ) + + self._prune_old_spans() + + def on_end(self, otel_span): + # type: (OTelSpan) -> None + client = get_client() + + if client.options["instrumenter"] != INSTRUMENTER.OTEL: + return + + span_context = otel_span.get_span_context() + if not span_context.is_valid: + return + + span_id = format_span_id(span_context.span_id) + sentry_span = self.otel_span_map.pop(span_id, None) + if not sentry_span: + return + + sentry_span.op = otel_span.name + + self._update_span_with_otel_status(sentry_span, otel_span) + + if isinstance(sentry_span, Transaction): + sentry_span.name = otel_span.name + sentry_span.set_context( + OPEN_TELEMETRY_CONTEXT, self._get_otel_context(otel_span) + ) + self._update_transaction_with_otel_data(sentry_span, otel_span) + + else: + self._update_span_with_otel_data(sentry_span, otel_span) + + end_timestamp = None + if otel_span.end_time is not None: + end_timestamp = datetime.fromtimestamp( + otel_span.end_time / 1e9, timezone.utc + ) # OTel spans have nanosecond precision + + sentry_span.finish(end_timestamp=end_timestamp) + + if otel_span.start_time is not None: + span_start_in_minutes = int( + otel_span.start_time / 1e9 / 60 + ) # OTel spans have nanosecond precision + self.open_spans.setdefault(span_start_in_minutes, set()).discard(span_id) + + self._prune_old_spans() + + def _is_sentry_span(self, otel_span): + # type: (OTelSpan) -> bool + """ + Break infinite loop: + HTTP requests to Sentry are caught by OTel and send again to Sentry. + """ + otel_span_url = None + if otel_span.attributes is not None: + otel_span_url = otel_span.attributes.get(SpanAttributes.HTTP_URL) + otel_span_url = cast("Optional[str]", otel_span_url) + + dsn_url = None + client = get_client() + if client.dsn: + dsn_url = Dsn(client.dsn).netloc + + if otel_span_url and dsn_url and dsn_url in otel_span_url: + return True + + return False + + def _get_otel_context(self, otel_span): + # type: (OTelSpan) -> dict[str, Any] + """ + Returns the OTel context for Sentry. + See: https://develop.sentry.dev/sdk/performance/opentelemetry/#step-5-add-opentelemetry-context + """ + ctx = {} + + if otel_span.attributes: + ctx["attributes"] = dict(otel_span.attributes) + + if otel_span.resource.attributes: + ctx["resource"] = dict(otel_span.resource.attributes) + + return ctx + + def _get_trace_data(self, otel_span, parent_context): + # type: (OTelSpan, Optional[context_api.Context]) -> dict[str, Any] + """ + Extracts tracing information from one OTel span and its parent OTel context. + """ + trace_data = {} # type: dict[str, Any] + span_context = otel_span.get_span_context() + + span_id = format_span_id(span_context.span_id) + trace_data["span_id"] = span_id + + trace_id = format_trace_id(span_context.trace_id) + trace_data["trace_id"] = trace_id + + parent_span_id = ( + format_span_id(otel_span.parent.span_id) if otel_span.parent else None + ) + trace_data["parent_span_id"] = parent_span_id + + sentry_trace_data = get_value(SENTRY_TRACE_KEY, parent_context) + sentry_trace_data = cast("dict[str, Union[str, bool, None]]", sentry_trace_data) + trace_data["parent_sampled"] = ( + sentry_trace_data["parent_sampled"] if sentry_trace_data else None + ) + + baggage = get_value(SENTRY_BAGGAGE_KEY, parent_context) + trace_data["baggage"] = baggage + + return trace_data + + def _update_span_with_otel_status(self, sentry_span, otel_span): + # type: (SentrySpan, OTelSpan) -> None + """ + Set the Sentry span status from the OTel span + """ + if otel_span.status.is_unset: + return + + if otel_span.status.is_ok: + sentry_span.set_status(SPANSTATUS.OK) + return + + sentry_span.set_status(SPANSTATUS.INTERNAL_ERROR) + + def _update_span_with_otel_data(self, sentry_span, otel_span): + # type: (SentrySpan, OTelSpan) -> None + """ + Convert OTel span data and update the Sentry span with it. + This should eventually happen on the server when ingesting the spans. + """ + sentry_span.set_data("otel.kind", otel_span.kind) + + op = otel_span.name + description = otel_span.name + + if otel_span.attributes is not None: + for key, val in otel_span.attributes.items(): + sentry_span.set_data(key, val) + + http_method = otel_span.attributes.get(SpanAttributes.HTTP_METHOD) + http_method = cast("Optional[str]", http_method) + + db_query = otel_span.attributes.get(SpanAttributes.DB_SYSTEM) + + if http_method: + op = "http" + + if otel_span.kind == SpanKind.SERVER: + op += ".server" + elif otel_span.kind == SpanKind.CLIENT: + op += ".client" + + description = http_method + + peer_name = otel_span.attributes.get(SpanAttributes.NET_PEER_NAME, None) + if peer_name: + description += " {}".format(peer_name) + + target = otel_span.attributes.get(SpanAttributes.HTTP_TARGET, None) + if target: + description += " {}".format(target) + + if not peer_name and not target: + url = otel_span.attributes.get(SpanAttributes.HTTP_URL, None) + url = cast("Optional[str]", url) + if url: + parsed_url = urlparse(url) + url = "{}://{}{}".format( + parsed_url.scheme, parsed_url.netloc, parsed_url.path + ) + description += " {}".format(url) + + status_code = otel_span.attributes.get( + SpanAttributes.HTTP_STATUS_CODE, None + ) + status_code = cast("Optional[int]", status_code) + if status_code: + sentry_span.set_http_status(status_code) + + elif db_query: + op = "db" + statement = otel_span.attributes.get(SpanAttributes.DB_STATEMENT, None) + statement = cast("Optional[str]", statement) + if statement: + description = statement + + sentry_span.op = op + sentry_span.description = description + + def _update_transaction_with_otel_data(self, sentry_span, otel_span): + # type: (SentrySpan, OTelSpan) -> None + if otel_span.attributes is None: + return + + http_method = otel_span.attributes.get(SpanAttributes.HTTP_METHOD) + + if http_method: + status_code = otel_span.attributes.get(SpanAttributes.HTTP_STATUS_CODE) + status_code = cast("Optional[int]", status_code) + if status_code: + sentry_span.set_http_status(status_code) + + op = "http" + + if otel_span.kind == SpanKind.SERVER: + op += ".server" + elif otel_span.kind == SpanKind.CLIENT: + op += ".client" + + sentry_span.op = op diff --git a/aws/lambda_demo/sentry_sdk/integrations/pure_eval.py b/aws/lambda_demo/sentry_sdk/integrations/pure_eval.py new file mode 100644 index 000000000..c1c3d6387 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/pure_eval.py @@ -0,0 +1,139 @@ +import ast + +import sentry_sdk +from sentry_sdk import serializer +from sentry_sdk.integrations import Integration, DidNotEnable +from sentry_sdk.scope import add_global_event_processor +from sentry_sdk.utils import walk_exception_chain, iter_stacks + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Optional, Dict, Any, Tuple, List + from types import FrameType + + from sentry_sdk._types import Event, Hint + +try: + import executing +except ImportError: + raise DidNotEnable("executing is not installed") + +try: + import pure_eval +except ImportError: + raise DidNotEnable("pure_eval is not installed") + +try: + # Used implicitly, just testing it's available + import asttokens # noqa +except ImportError: + raise DidNotEnable("asttokens is not installed") + + +class PureEvalIntegration(Integration): + identifier = "pure_eval" + + @staticmethod + def setup_once(): + # type: () -> None + + @add_global_event_processor + def add_executing_info(event, hint): + # type: (Event, Optional[Hint]) -> Optional[Event] + if sentry_sdk.get_client().get_integration(PureEvalIntegration) is None: + return event + + if hint is None: + return event + + exc_info = hint.get("exc_info", None) + + if exc_info is None: + return event + + exception = event.get("exception", None) + + if exception is None: + return event + + values = exception.get("values", None) + + if values is None: + return event + + for exception, (_exc_type, _exc_value, exc_tb) in zip( + reversed(values), walk_exception_chain(exc_info) + ): + sentry_frames = [ + frame + for frame in exception.get("stacktrace", {}).get("frames", []) + if frame.get("function") + ] + tbs = list(iter_stacks(exc_tb)) + if len(sentry_frames) != len(tbs): + continue + + for sentry_frame, tb in zip(sentry_frames, tbs): + sentry_frame["vars"] = ( + pure_eval_frame(tb.tb_frame) or sentry_frame["vars"] + ) + return event + + +def pure_eval_frame(frame): + # type: (FrameType) -> Dict[str, Any] + source = executing.Source.for_frame(frame) + if not source.tree: + return {} + + statements = source.statements_at_line(frame.f_lineno) + if not statements: + return {} + + scope = stmt = list(statements)[0] + while True: + # Get the parent first in case the original statement is already + # a function definition, e.g. if we're calling a decorator + # In that case we still want the surrounding scope, not that function + scope = scope.parent + if isinstance(scope, (ast.FunctionDef, ast.ClassDef, ast.Module)): + break + + evaluator = pure_eval.Evaluator.from_frame(frame) + expressions = evaluator.interesting_expressions_grouped(scope) + + def closeness(expression): + # type: (Tuple[List[Any], Any]) -> Tuple[int, int] + # Prioritise expressions with a node closer to the statement executed + # without being after that statement + # A higher return value is better - the expression will appear + # earlier in the list of values and is less likely to be trimmed + nodes, _value = expression + + def start(n): + # type: (ast.expr) -> Tuple[int, int] + return (n.lineno, n.col_offset) + + nodes_before_stmt = [ + node for node in nodes if start(node) < stmt.last_token.end # type: ignore + ] + if nodes_before_stmt: + # The position of the last node before or in the statement + return max(start(node) for node in nodes_before_stmt) + else: + # The position of the first node after the statement + # Negative means it's always lower priority than nodes that come before + # Less negative means closer to the statement and higher priority + lineno, col_offset = min(start(node) for node in nodes) + return (-lineno, -col_offset) + + # This adds the first_token and last_token attributes to nodes + atok = source.asttokens() + + expressions.sort(key=closeness, reverse=True) + vars = { + atok.get_text(nodes[0]): value + for nodes, value in expressions[: serializer.MAX_DATABAG_BREADTH] + } + return serializer.serialize(vars, is_vars=True) diff --git a/aws/lambda_demo/sentry_sdk/integrations/pymongo.py b/aws/lambda_demo/sentry_sdk/integrations/pymongo.py new file mode 100644 index 000000000..f65ad7368 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/pymongo.py @@ -0,0 +1,214 @@ +import copy +import json + +import sentry_sdk +from sentry_sdk.consts import SPANSTATUS, SPANDATA, OP +from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import Span +from sentry_sdk.utils import capture_internal_exceptions + +try: + from pymongo import monitoring +except ImportError: + raise DidNotEnable("Pymongo not installed") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Dict, Union + + from pymongo.monitoring import ( + CommandFailedEvent, + CommandStartedEvent, + CommandSucceededEvent, + ) + + +SAFE_COMMAND_ATTRIBUTES = [ + "insert", + "ordered", + "find", + "limit", + "singleBatch", + "aggregate", + "createIndexes", + "indexes", + "delete", + "findAndModify", + "renameCollection", + "to", + "drop", +] + + +def _strip_pii(command): + # type: (Dict[str, Any]) -> Dict[str, Any] + for key in command: + is_safe_field = key in SAFE_COMMAND_ATTRIBUTES + if is_safe_field: + # Skip if safe key + continue + + update_db_command = key == "update" and "findAndModify" not in command + if update_db_command: + # Also skip "update" db command because it is save. + # There is also an "update" key in the "findAndModify" command, which is NOT safe! + continue + + # Special stripping for documents + is_document = key == "documents" + if is_document: + for doc in command[key]: + for doc_key in doc: + doc[doc_key] = "%s" + continue + + # Special stripping for dict style fields + is_dict_field = key in ["filter", "query", "update"] + if is_dict_field: + for item_key in command[key]: + command[key][item_key] = "%s" + continue + + # For pipeline fields strip the `$match` dict + is_pipeline_field = key == "pipeline" + if is_pipeline_field: + for pipeline in command[key]: + for match_key in pipeline["$match"] if "$match" in pipeline else []: + pipeline["$match"][match_key] = "%s" + continue + + # Default stripping + command[key] = "%s" + + return command + + +def _get_db_data(event): + # type: (Any) -> Dict[str, Any] + data = {} + + data[SPANDATA.DB_SYSTEM] = "mongodb" + + db_name = event.database_name + if db_name is not None: + data[SPANDATA.DB_NAME] = db_name + + server_address = event.connection_id[0] + if server_address is not None: + data[SPANDATA.SERVER_ADDRESS] = server_address + + server_port = event.connection_id[1] + if server_port is not None: + data[SPANDATA.SERVER_PORT] = server_port + + return data + + +class CommandTracer(monitoring.CommandListener): + def __init__(self): + # type: () -> None + self._ongoing_operations = {} # type: Dict[int, Span] + + def _operation_key(self, event): + # type: (Union[CommandFailedEvent, CommandStartedEvent, CommandSucceededEvent]) -> int + return event.request_id + + def started(self, event): + # type: (CommandStartedEvent) -> None + if sentry_sdk.get_client().get_integration(PyMongoIntegration) is None: + return + + with capture_internal_exceptions(): + command = dict(copy.deepcopy(event.command)) + + command.pop("$db", None) + command.pop("$clusterTime", None) + command.pop("$signature", None) + + tags = { + "db.name": event.database_name, + SPANDATA.DB_SYSTEM: "mongodb", + SPANDATA.DB_OPERATION: event.command_name, + SPANDATA.DB_MONGODB_COLLECTION: command.get(event.command_name), + } + + try: + tags["net.peer.name"] = event.connection_id[0] + tags["net.peer.port"] = str(event.connection_id[1]) + except TypeError: + pass + + data = {"operation_ids": {}} # type: Dict[str, Any] + data["operation_ids"]["operation"] = event.operation_id + data["operation_ids"]["request"] = event.request_id + + data.update(_get_db_data(event)) + + try: + lsid = command.pop("lsid")["id"] + data["operation_ids"]["session"] = str(lsid) + except KeyError: + pass + + if not should_send_default_pii(): + command = _strip_pii(command) + + query = json.dumps(command, default=str) + span = sentry_sdk.start_span( + op=OP.DB, + name=query, + origin=PyMongoIntegration.origin, + ) + + for tag, value in tags.items(): + # set the tag for backwards-compatibility. + # TODO: remove the set_tag call in the next major release! + span.set_tag(tag, value) + + span.set_data(tag, value) + + for key, value in data.items(): + span.set_data(key, value) + + with capture_internal_exceptions(): + sentry_sdk.add_breadcrumb( + message=query, category="query", type=OP.DB, data=tags + ) + + self._ongoing_operations[self._operation_key(event)] = span.__enter__() + + def failed(self, event): + # type: (CommandFailedEvent) -> None + if sentry_sdk.get_client().get_integration(PyMongoIntegration) is None: + return + + try: + span = self._ongoing_operations.pop(self._operation_key(event)) + span.set_status(SPANSTATUS.INTERNAL_ERROR) + span.__exit__(None, None, None) + except KeyError: + return + + def succeeded(self, event): + # type: (CommandSucceededEvent) -> None + if sentry_sdk.get_client().get_integration(PyMongoIntegration) is None: + return + + try: + span = self._ongoing_operations.pop(self._operation_key(event)) + span.set_status(SPANSTATUS.OK) + span.__exit__(None, None, None) + except KeyError: + pass + + +class PyMongoIntegration(Integration): + identifier = "pymongo" + origin = f"auto.db.{identifier}" + + @staticmethod + def setup_once(): + # type: () -> None + monitoring.register(CommandTracer()) diff --git a/aws/lambda_demo/sentry_sdk/integrations/pyramid.py b/aws/lambda_demo/sentry_sdk/integrations/pyramid.py new file mode 100644 index 000000000..d1475ada6 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/pyramid.py @@ -0,0 +1,229 @@ +import functools +import os +import sys +import weakref + +import sentry_sdk +from sentry_sdk.integrations import Integration, DidNotEnable +from sentry_sdk.integrations._wsgi_common import RequestExtractor +from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import SOURCE_FOR_STYLE +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + reraise, +) + +try: + from pyramid.httpexceptions import HTTPException + from pyramid.request import Request +except ImportError: + raise DidNotEnable("Pyramid not installed") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pyramid.response import Response + from typing import Any + from sentry_sdk.integrations.wsgi import _ScopedResponse + from typing import Callable + from typing import Dict + from typing import Optional + from webob.cookies import RequestCookies + from webob.request import _FieldStorageWithFile + + from sentry_sdk.utils import ExcInfo + from sentry_sdk._types import Event, EventProcessor + + +if getattr(Request, "authenticated_userid", None): + + def authenticated_userid(request): + # type: (Request) -> Optional[Any] + return request.authenticated_userid + +else: + # bw-compat for pyramid < 1.5 + from pyramid.security import authenticated_userid # type: ignore + + +TRANSACTION_STYLE_VALUES = ("route_name", "route_pattern") + + +class PyramidIntegration(Integration): + identifier = "pyramid" + origin = f"auto.http.{identifier}" + + transaction_style = "" + + def __init__(self, transaction_style="route_name"): + # type: (str) -> None + if transaction_style not in TRANSACTION_STYLE_VALUES: + raise ValueError( + "Invalid value for transaction_style: %s (must be in %s)" + % (transaction_style, TRANSACTION_STYLE_VALUES) + ) + self.transaction_style = transaction_style + + @staticmethod + def setup_once(): + # type: () -> None + from pyramid import router + + old_call_view = router._call_view + + @functools.wraps(old_call_view) + def sentry_patched_call_view(registry, request, *args, **kwargs): + # type: (Any, Request, *Any, **Any) -> Response + integration = sentry_sdk.get_client().get_integration(PyramidIntegration) + if integration is None: + return old_call_view(registry, request, *args, **kwargs) + + _set_transaction_name_and_source( + sentry_sdk.get_current_scope(), integration.transaction_style, request + ) + scope = sentry_sdk.get_isolation_scope() + scope.add_event_processor( + _make_event_processor(weakref.ref(request), integration) + ) + + return old_call_view(registry, request, *args, **kwargs) + + router._call_view = sentry_patched_call_view + + if hasattr(Request, "invoke_exception_view"): + old_invoke_exception_view = Request.invoke_exception_view + + def sentry_patched_invoke_exception_view(self, *args, **kwargs): + # type: (Request, *Any, **Any) -> Any + rv = old_invoke_exception_view(self, *args, **kwargs) + + if ( + self.exc_info + and all(self.exc_info) + and rv.status_int == 500 + and sentry_sdk.get_client().get_integration(PyramidIntegration) + is not None + ): + _capture_exception(self.exc_info) + + return rv + + Request.invoke_exception_view = sentry_patched_invoke_exception_view + + old_wsgi_call = router.Router.__call__ + + @ensure_integration_enabled(PyramidIntegration, old_wsgi_call) + def sentry_patched_wsgi_call(self, environ, start_response): + # type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse + def sentry_patched_inner_wsgi_call(environ, start_response): + # type: (Dict[str, Any], Callable[..., Any]) -> Any + try: + return old_wsgi_call(self, environ, start_response) + except Exception: + einfo = sys.exc_info() + _capture_exception(einfo) + reraise(*einfo) + + middleware = SentryWsgiMiddleware( + sentry_patched_inner_wsgi_call, + span_origin=PyramidIntegration.origin, + ) + return middleware(environ, start_response) + + router.Router.__call__ = sentry_patched_wsgi_call + + +@ensure_integration_enabled(PyramidIntegration) +def _capture_exception(exc_info): + # type: (ExcInfo) -> None + if exc_info[0] is None or issubclass(exc_info[0], HTTPException): + return + + event, hint = event_from_exception( + exc_info, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "pyramid", "handled": False}, + ) + + sentry_sdk.capture_event(event, hint=hint) + + +def _set_transaction_name_and_source(scope, transaction_style, request): + # type: (sentry_sdk.Scope, str, Request) -> None + try: + name_for_style = { + "route_name": request.matched_route.name, + "route_pattern": request.matched_route.pattern, + } + scope.set_transaction_name( + name_for_style[transaction_style], + source=SOURCE_FOR_STYLE[transaction_style], + ) + except Exception: + pass + + +class PyramidRequestExtractor(RequestExtractor): + def url(self): + # type: () -> str + return self.request.path_url + + def env(self): + # type: () -> Dict[str, str] + return self.request.environ + + def cookies(self): + # type: () -> RequestCookies + return self.request.cookies + + def raw_data(self): + # type: () -> str + return self.request.text + + def form(self): + # type: () -> Dict[str, str] + return { + key: value + for key, value in self.request.POST.items() + if not getattr(value, "filename", None) + } + + def files(self): + # type: () -> Dict[str, _FieldStorageWithFile] + return { + key: value + for key, value in self.request.POST.items() + if getattr(value, "filename", None) + } + + def size_of_file(self, postdata): + # type: (_FieldStorageWithFile) -> int + file = postdata.file + try: + return os.fstat(file.fileno()).st_size + except Exception: + return 0 + + +def _make_event_processor(weak_request, integration): + # type: (Callable[[], Request], PyramidIntegration) -> EventProcessor + def pyramid_event_processor(event, hint): + # type: (Event, Dict[str, Any]) -> Event + request = weak_request() + if request is None: + return event + + with capture_internal_exceptions(): + PyramidRequestExtractor(request).extract_into_event(event) + + if should_send_default_pii(): + with capture_internal_exceptions(): + user_info = event.setdefault("user", {}) + user_info.setdefault("id", authenticated_userid(request)) + + return event + + return pyramid_event_processor diff --git a/aws/lambda_demo/sentry_sdk/integrations/quart.py b/aws/lambda_demo/sentry_sdk/integrations/quart.py new file mode 100644 index 000000000..51306bb4c --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/quart.py @@ -0,0 +1,237 @@ +import asyncio +import inspect +from functools import wraps + +import sentry_sdk +from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.integrations._wsgi_common import _filter_headers +from sentry_sdk.integrations.asgi import SentryAsgiMiddleware +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import SOURCE_FOR_STYLE +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, +) +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Union + + from sentry_sdk._types import Event, EventProcessor + +try: + import quart_auth # type: ignore +except ImportError: + quart_auth = None + +try: + from quart import ( # type: ignore + has_request_context, + has_websocket_context, + Request, + Quart, + request, + websocket, + ) + from quart.signals import ( # type: ignore + got_background_exception, + got_request_exception, + got_websocket_exception, + request_started, + websocket_started, + ) +except ImportError: + raise DidNotEnable("Quart is not installed") +else: + # Quart 0.19 is based on Flask and hence no longer has a Scaffold + try: + from quart.scaffold import Scaffold # type: ignore + except ImportError: + from flask.sansio.scaffold import Scaffold # type: ignore + +TRANSACTION_STYLE_VALUES = ("endpoint", "url") + + +class QuartIntegration(Integration): + identifier = "quart" + origin = f"auto.http.{identifier}" + + transaction_style = "" + + def __init__(self, transaction_style="endpoint"): + # type: (str) -> None + if transaction_style not in TRANSACTION_STYLE_VALUES: + raise ValueError( + "Invalid value for transaction_style: %s (must be in %s)" + % (transaction_style, TRANSACTION_STYLE_VALUES) + ) + self.transaction_style = transaction_style + + @staticmethod + def setup_once(): + # type: () -> None + + request_started.connect(_request_websocket_started) + websocket_started.connect(_request_websocket_started) + got_background_exception.connect(_capture_exception) + got_request_exception.connect(_capture_exception) + got_websocket_exception.connect(_capture_exception) + + patch_asgi_app() + patch_scaffold_route() + + +def patch_asgi_app(): + # type: () -> None + old_app = Quart.__call__ + + async def sentry_patched_asgi_app(self, scope, receive, send): + # type: (Any, Any, Any, Any) -> Any + if sentry_sdk.get_client().get_integration(QuartIntegration) is None: + return await old_app(self, scope, receive, send) + + middleware = SentryAsgiMiddleware( + lambda *a, **kw: old_app(self, *a, **kw), + span_origin=QuartIntegration.origin, + ) + middleware.__call__ = middleware._run_asgi3 + return await middleware(scope, receive, send) + + Quart.__call__ = sentry_patched_asgi_app + + +def patch_scaffold_route(): + # type: () -> None + old_route = Scaffold.route + + def _sentry_route(*args, **kwargs): + # type: (*Any, **Any) -> Any + old_decorator = old_route(*args, **kwargs) + + def decorator(old_func): + # type: (Any) -> Any + + if inspect.isfunction(old_func) and not asyncio.iscoroutinefunction( + old_func + ): + + @wraps(old_func) + @ensure_integration_enabled(QuartIntegration, old_func) + def _sentry_func(*args, **kwargs): + # type: (*Any, **Any) -> Any + current_scope = sentry_sdk.get_current_scope() + if current_scope.transaction is not None: + current_scope.transaction.update_active_thread() + + sentry_scope = sentry_sdk.get_isolation_scope() + if sentry_scope.profile is not None: + sentry_scope.profile.update_active_thread_id() + + return old_func(*args, **kwargs) + + return old_decorator(_sentry_func) + + return old_decorator(old_func) + + return decorator + + Scaffold.route = _sentry_route + + +def _set_transaction_name_and_source(scope, transaction_style, request): + # type: (sentry_sdk.Scope, str, Request) -> None + + try: + name_for_style = { + "url": request.url_rule.rule, + "endpoint": request.url_rule.endpoint, + } + scope.set_transaction_name( + name_for_style[transaction_style], + source=SOURCE_FOR_STYLE[transaction_style], + ) + except Exception: + pass + + +async def _request_websocket_started(app, **kwargs): + # type: (Quart, **Any) -> None + integration = sentry_sdk.get_client().get_integration(QuartIntegration) + if integration is None: + return + + if has_request_context(): + request_websocket = request._get_current_object() + if has_websocket_context(): + request_websocket = websocket._get_current_object() + + # Set the transaction name here, but rely on ASGI middleware + # to actually start the transaction + _set_transaction_name_and_source( + sentry_sdk.get_current_scope(), integration.transaction_style, request_websocket + ) + + scope = sentry_sdk.get_isolation_scope() + evt_processor = _make_request_event_processor(app, request_websocket, integration) + scope.add_event_processor(evt_processor) + + +def _make_request_event_processor(app, request, integration): + # type: (Quart, Request, QuartIntegration) -> EventProcessor + def inner(event, hint): + # type: (Event, dict[str, Any]) -> Event + # if the request is gone we are fine not logging the data from + # it. This might happen if the processor is pushed away to + # another thread. + if request is None: + return event + + with capture_internal_exceptions(): + # TODO: Figure out what to do with request body. Methods on request + # are async, but event processors are not. + + request_info = event.setdefault("request", {}) + request_info["url"] = request.url + request_info["query_string"] = request.query_string + request_info["method"] = request.method + request_info["headers"] = _filter_headers(dict(request.headers)) + + if should_send_default_pii(): + request_info["env"] = {"REMOTE_ADDR": request.access_route[0]} + _add_user_to_event(event) + + return event + + return inner + + +async def _capture_exception(sender, exception, **kwargs): + # type: (Quart, Union[ValueError, BaseException], **Any) -> None + integration = sentry_sdk.get_client().get_integration(QuartIntegration) + if integration is None: + return + + event, hint = event_from_exception( + exception, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "quart", "handled": False}, + ) + + sentry_sdk.capture_event(event, hint=hint) + + +def _add_user_to_event(event): + # type: (Event) -> None + if quart_auth is None: + return + + user = quart_auth.current_user + if user is None: + return + + with capture_internal_exceptions(): + user_info = event.setdefault("user", {}) + + user_info["id"] = quart_auth.current_user._auth_id diff --git a/aws/lambda_demo/sentry_sdk/integrations/ray.py b/aws/lambda_demo/sentry_sdk/integrations/ray.py new file mode 100644 index 000000000..24a28c307 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/ray.py @@ -0,0 +1,141 @@ +import inspect +import sys + +import sentry_sdk +from sentry_sdk.consts import OP, SPANSTATUS +from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration +from sentry_sdk.tracing import TRANSACTION_SOURCE_TASK +from sentry_sdk.utils import ( + event_from_exception, + logger, + package_version, + qualname_from_function, + reraise, +) + +try: + import ray # type: ignore[import-not-found] +except ImportError: + raise DidNotEnable("Ray not installed.") +import functools + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Callable + from typing import Any, Optional + from sentry_sdk.utils import ExcInfo + + +def _check_sentry_initialized(): + # type: () -> None + if sentry_sdk.get_client().is_active(): + return + + logger.debug( + "[Tracing] Sentry not initialized in ray cluster worker, performance data will be discarded." + ) + + +def _patch_ray_remote(): + # type: () -> None + old_remote = ray.remote + + @functools.wraps(old_remote) + def new_remote(f, *args, **kwargs): + # type: (Callable[..., Any], *Any, **Any) -> Callable[..., Any] + if inspect.isclass(f): + # Ray Actors + # (https://docs.ray.io/en/latest/ray-core/actors.html) + # are not supported + # (Only Ray Tasks are supported) + return old_remote(f, *args, *kwargs) + + def _f(*f_args, _tracing=None, **f_kwargs): + # type: (Any, Optional[dict[str, Any]], Any) -> Any + """ + Ray Worker + """ + _check_sentry_initialized() + + transaction = sentry_sdk.continue_trace( + _tracing or {}, + op=OP.QUEUE_TASK_RAY, + name=qualname_from_function(f), + origin=RayIntegration.origin, + source=TRANSACTION_SOURCE_TASK, + ) + + with sentry_sdk.start_transaction(transaction) as transaction: + try: + result = f(*f_args, **f_kwargs) + transaction.set_status(SPANSTATUS.OK) + except Exception: + transaction.set_status(SPANSTATUS.INTERNAL_ERROR) + exc_info = sys.exc_info() + _capture_exception(exc_info) + reraise(*exc_info) + + return result + + rv = old_remote(_f, *args, *kwargs) + old_remote_method = rv.remote + + def _remote_method_with_header_propagation(*args, **kwargs): + # type: (*Any, **Any) -> Any + """ + Ray Client + """ + with sentry_sdk.start_span( + op=OP.QUEUE_SUBMIT_RAY, + name=qualname_from_function(f), + origin=RayIntegration.origin, + ) as span: + tracing = { + k: v + for k, v in sentry_sdk.get_current_scope().iter_trace_propagation_headers() + } + try: + result = old_remote_method(*args, **kwargs, _tracing=tracing) + span.set_status(SPANSTATUS.OK) + except Exception: + span.set_status(SPANSTATUS.INTERNAL_ERROR) + exc_info = sys.exc_info() + _capture_exception(exc_info) + reraise(*exc_info) + + return result + + rv.remote = _remote_method_with_header_propagation + + return rv + + ray.remote = new_remote + + +def _capture_exception(exc_info, **kwargs): + # type: (ExcInfo, **Any) -> None + client = sentry_sdk.get_client() + + event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={ + "handled": False, + "type": RayIntegration.identifier, + }, + ) + sentry_sdk.capture_event(event, hint=hint) + + +class RayIntegration(Integration): + identifier = "ray" + origin = f"auto.queue.{identifier}" + + @staticmethod + def setup_once(): + # type: () -> None + version = package_version("ray") + _check_minimum_version(RayIntegration, version) + + _patch_ray_remote() diff --git a/aws/lambda_demo/sentry_sdk/integrations/redis/__init__.py b/aws/lambda_demo/sentry_sdk/integrations/redis/__init__.py new file mode 100644 index 000000000..f44313829 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/redis/__init__.py @@ -0,0 +1,38 @@ +from sentry_sdk.integrations import Integration, DidNotEnable +from sentry_sdk.integrations.redis.consts import _DEFAULT_MAX_DATA_SIZE +from sentry_sdk.integrations.redis.rb import _patch_rb +from sentry_sdk.integrations.redis.redis import _patch_redis +from sentry_sdk.integrations.redis.redis_cluster import _patch_redis_cluster +from sentry_sdk.integrations.redis.redis_py_cluster_legacy import _patch_rediscluster +from sentry_sdk.utils import logger + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Optional + + +class RedisIntegration(Integration): + identifier = "redis" + + def __init__(self, max_data_size=_DEFAULT_MAX_DATA_SIZE, cache_prefixes=None): + # type: (int, Optional[list[str]]) -> None + self.max_data_size = max_data_size + self.cache_prefixes = cache_prefixes if cache_prefixes is not None else [] + + @staticmethod + def setup_once(): + # type: () -> None + try: + from redis import StrictRedis, client + except ImportError: + raise DidNotEnable("Redis client not installed") + + _patch_redis(StrictRedis, client) + _patch_redis_cluster() + _patch_rb() + + try: + _patch_rediscluster() + except Exception: + logger.exception("Error occurred while patching `rediscluster` library") diff --git a/aws/lambda_demo/sentry_sdk/integrations/redis/_async_common.py b/aws/lambda_demo/sentry_sdk/integrations/redis/_async_common.py new file mode 100644 index 000000000..196e85e74 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/redis/_async_common.py @@ -0,0 +1,108 @@ +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN +from sentry_sdk.integrations.redis.modules.caches import ( + _compile_cache_span_properties, + _set_cache_data, +) +from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties +from sentry_sdk.integrations.redis.utils import ( + _set_client_data, + _set_pipeline_data, +) +from sentry_sdk.tracing import Span +from sentry_sdk.utils import capture_internal_exceptions + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Callable + from typing import Any, Union + from redis.asyncio.client import Pipeline, StrictRedis + from redis.asyncio.cluster import ClusterPipeline, RedisCluster + + +def patch_redis_async_pipeline( + pipeline_cls, is_cluster, get_command_args_fn, set_db_data_fn +): + # type: (Union[type[Pipeline[Any]], type[ClusterPipeline[Any]]], bool, Any, Callable[[Span, Any], None]) -> None + old_execute = pipeline_cls.execute + + from sentry_sdk.integrations.redis import RedisIntegration + + async def _sentry_execute(self, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + if sentry_sdk.get_client().get_integration(RedisIntegration) is None: + return await old_execute(self, *args, **kwargs) + + with sentry_sdk.start_span( + op=OP.DB_REDIS, + name="redis.pipeline.execute", + origin=SPAN_ORIGIN, + ) as span: + with capture_internal_exceptions(): + set_db_data_fn(span, self) + _set_pipeline_data( + span, + is_cluster, + get_command_args_fn, + False if is_cluster else self.is_transaction, + self._command_stack if is_cluster else self.command_stack, + ) + + return await old_execute(self, *args, **kwargs) + + pipeline_cls.execute = _sentry_execute # type: ignore + + +def patch_redis_async_client(cls, is_cluster, set_db_data_fn): + # type: (Union[type[StrictRedis[Any]], type[RedisCluster[Any]]], bool, Callable[[Span, Any], None]) -> None + old_execute_command = cls.execute_command + + from sentry_sdk.integrations.redis import RedisIntegration + + async def _sentry_execute_command(self, name, *args, **kwargs): + # type: (Any, str, *Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(RedisIntegration) + if integration is None: + return await old_execute_command(self, name, *args, **kwargs) + + cache_properties = _compile_cache_span_properties( + name, + args, + kwargs, + integration, + ) + + cache_span = None + if cache_properties["is_cache_key"] and cache_properties["op"] is not None: + cache_span = sentry_sdk.start_span( + op=cache_properties["op"], + name=cache_properties["description"], + origin=SPAN_ORIGIN, + ) + cache_span.__enter__() + + db_properties = _compile_db_span_properties(integration, name, args) + + db_span = sentry_sdk.start_span( + op=db_properties["op"], + name=db_properties["description"], + origin=SPAN_ORIGIN, + ) + db_span.__enter__() + + set_db_data_fn(db_span, self) + _set_client_data(db_span, is_cluster, name, *args) + + value = await old_execute_command(self, name, *args, **kwargs) + + db_span.__exit__(None, None, None) + + if cache_span: + _set_cache_data(cache_span, self, cache_properties, value) + cache_span.__exit__(None, None, None) + + return value + + cls.execute_command = _sentry_execute_command # type: ignore diff --git a/aws/lambda_demo/sentry_sdk/integrations/redis/_sync_common.py b/aws/lambda_demo/sentry_sdk/integrations/redis/_sync_common.py new file mode 100644 index 000000000..ef10e9e4f --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/redis/_sync_common.py @@ -0,0 +1,113 @@ +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN +from sentry_sdk.integrations.redis.modules.caches import ( + _compile_cache_span_properties, + _set_cache_data, +) +from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties +from sentry_sdk.integrations.redis.utils import ( + _set_client_data, + _set_pipeline_data, +) +from sentry_sdk.tracing import Span +from sentry_sdk.utils import capture_internal_exceptions + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Callable + from typing import Any + + +def patch_redis_pipeline( + pipeline_cls, + is_cluster, + get_command_args_fn, + set_db_data_fn, +): + # type: (Any, bool, Any, Callable[[Span, Any], None]) -> None + old_execute = pipeline_cls.execute + + from sentry_sdk.integrations.redis import RedisIntegration + + def sentry_patched_execute(self, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any + if sentry_sdk.get_client().get_integration(RedisIntegration) is None: + return old_execute(self, *args, **kwargs) + + with sentry_sdk.start_span( + op=OP.DB_REDIS, + name="redis.pipeline.execute", + origin=SPAN_ORIGIN, + ) as span: + with capture_internal_exceptions(): + set_db_data_fn(span, self) + _set_pipeline_data( + span, + is_cluster, + get_command_args_fn, + False if is_cluster else self.transaction, + self.command_stack, + ) + + return old_execute(self, *args, **kwargs) + + pipeline_cls.execute = sentry_patched_execute + + +def patch_redis_client(cls, is_cluster, set_db_data_fn): + # type: (Any, bool, Callable[[Span, Any], None]) -> None + """ + This function can be used to instrument custom redis client classes or + subclasses. + """ + old_execute_command = cls.execute_command + + from sentry_sdk.integrations.redis import RedisIntegration + + def sentry_patched_execute_command(self, name, *args, **kwargs): + # type: (Any, str, *Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(RedisIntegration) + if integration is None: + return old_execute_command(self, name, *args, **kwargs) + + cache_properties = _compile_cache_span_properties( + name, + args, + kwargs, + integration, + ) + + cache_span = None + if cache_properties["is_cache_key"] and cache_properties["op"] is not None: + cache_span = sentry_sdk.start_span( + op=cache_properties["op"], + name=cache_properties["description"], + origin=SPAN_ORIGIN, + ) + cache_span.__enter__() + + db_properties = _compile_db_span_properties(integration, name, args) + + db_span = sentry_sdk.start_span( + op=db_properties["op"], + name=db_properties["description"], + origin=SPAN_ORIGIN, + ) + db_span.__enter__() + + set_db_data_fn(db_span, self) + _set_client_data(db_span, is_cluster, name, *args) + + value = old_execute_command(self, name, *args, **kwargs) + + db_span.__exit__(None, None, None) + + if cache_span: + _set_cache_data(cache_span, self, cache_properties, value) + cache_span.__exit__(None, None, None) + + return value + + cls.execute_command = sentry_patched_execute_command diff --git a/aws/lambda_demo/sentry_sdk/integrations/redis/consts.py b/aws/lambda_demo/sentry_sdk/integrations/redis/consts.py new file mode 100644 index 000000000..737e82973 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/redis/consts.py @@ -0,0 +1,19 @@ +SPAN_ORIGIN = "auto.db.redis" + +_SINGLE_KEY_COMMANDS = frozenset( + ["decr", "decrby", "get", "incr", "incrby", "pttl", "set", "setex", "setnx", "ttl"], +) +_MULTI_KEY_COMMANDS = frozenset( + [ + "del", + "touch", + "unlink", + "mget", + ], +) +_COMMANDS_INCLUDING_SENSITIVE_DATA = [ + "auth", +] +_MAX_NUM_ARGS = 10 # Trim argument lists to this many values +_MAX_NUM_COMMANDS = 10 # Trim command lists to this many values +_DEFAULT_MAX_DATA_SIZE = 1024 diff --git a/aws/lambda_demo/sentry_sdk/integrations/redis/modules/__init__.py b/aws/lambda_demo/sentry_sdk/integrations/redis/modules/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/aws/lambda_demo/sentry_sdk/integrations/redis/modules/caches.py b/aws/lambda_demo/sentry_sdk/integrations/redis/modules/caches.py new file mode 100644 index 000000000..c6fc19f5b --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/redis/modules/caches.py @@ -0,0 +1,121 @@ +""" +Code used for the Caches module in Sentry +""" + +from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.integrations.redis.utils import _get_safe_key, _key_as_string +from sentry_sdk.utils import capture_internal_exceptions + +GET_COMMANDS = ("get", "mget") +SET_COMMANDS = ("set", "setex") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from sentry_sdk.integrations.redis import RedisIntegration + from sentry_sdk.tracing import Span + from typing import Any, Optional + + +def _get_op(name): + # type: (str) -> Optional[str] + op = None + if name.lower() in GET_COMMANDS: + op = OP.CACHE_GET + elif name.lower() in SET_COMMANDS: + op = OP.CACHE_PUT + + return op + + +def _compile_cache_span_properties(redis_command, args, kwargs, integration): + # type: (str, tuple[Any, ...], dict[str, Any], RedisIntegration) -> dict[str, Any] + key = _get_safe_key(redis_command, args, kwargs) + key_as_string = _key_as_string(key) + keys_as_string = key_as_string.split(", ") + + is_cache_key = False + for prefix in integration.cache_prefixes: + for kee in keys_as_string: + if kee.startswith(prefix): + is_cache_key = True + break + if is_cache_key: + break + + value = None + if redis_command.lower() in SET_COMMANDS: + value = args[-1] + + properties = { + "op": _get_op(redis_command), + "description": _get_cache_span_description( + redis_command, args, kwargs, integration + ), + "key": key, + "key_as_string": key_as_string, + "redis_command": redis_command.lower(), + "is_cache_key": is_cache_key, + "value": value, + } + + return properties + + +def _get_cache_span_description(redis_command, args, kwargs, integration): + # type: (str, tuple[Any, ...], dict[str, Any], RedisIntegration) -> str + description = _key_as_string(_get_safe_key(redis_command, args, kwargs)) + + data_should_be_truncated = ( + integration.max_data_size and len(description) > integration.max_data_size + ) + if data_should_be_truncated: + description = description[: integration.max_data_size - len("...")] + "..." + + return description + + +def _set_cache_data(span, redis_client, properties, return_value): + # type: (Span, Any, dict[str, Any], Optional[Any]) -> None + with capture_internal_exceptions(): + span.set_data(SPANDATA.CACHE_KEY, properties["key"]) + + if properties["redis_command"] in GET_COMMANDS: + if return_value is not None: + span.set_data(SPANDATA.CACHE_HIT, True) + size = ( + len(str(return_value).encode("utf-8")) + if not isinstance(return_value, bytes) + else len(return_value) + ) + span.set_data(SPANDATA.CACHE_ITEM_SIZE, size) + else: + span.set_data(SPANDATA.CACHE_HIT, False) + + elif properties["redis_command"] in SET_COMMANDS: + if properties["value"] is not None: + size = ( + len(properties["value"].encode("utf-8")) + if not isinstance(properties["value"], bytes) + else len(properties["value"]) + ) + span.set_data(SPANDATA.CACHE_ITEM_SIZE, size) + + try: + connection_params = redis_client.connection_pool.connection_kwargs + except AttributeError: + # If it is a cluster, there is no connection_pool attribute so we + # need to get the default node from the cluster instance + default_node = redis_client.get_default_node() + connection_params = { + "host": default_node.host, + "port": default_node.port, + } + + host = connection_params.get("host") + if host is not None: + span.set_data(SPANDATA.NETWORK_PEER_ADDRESS, host) + + port = connection_params.get("port") + if port is not None: + span.set_data(SPANDATA.NETWORK_PEER_PORT, port) diff --git a/aws/lambda_demo/sentry_sdk/integrations/redis/modules/queries.py b/aws/lambda_demo/sentry_sdk/integrations/redis/modules/queries.py new file mode 100644 index 000000000..e0d85a4ef --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/redis/modules/queries.py @@ -0,0 +1,68 @@ +""" +Code used for the Queries module in Sentry +""" + +from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.integrations.redis.utils import _get_safe_command +from sentry_sdk.utils import capture_internal_exceptions + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from redis import Redis + from sentry_sdk.integrations.redis import RedisIntegration + from sentry_sdk.tracing import Span + from typing import Any + + +def _compile_db_span_properties(integration, redis_command, args): + # type: (RedisIntegration, str, tuple[Any, ...]) -> dict[str, Any] + description = _get_db_span_description(integration, redis_command, args) + + properties = { + "op": OP.DB_REDIS, + "description": description, + } + + return properties + + +def _get_db_span_description(integration, command_name, args): + # type: (RedisIntegration, str, tuple[Any, ...]) -> str + description = command_name + + with capture_internal_exceptions(): + description = _get_safe_command(command_name, args) + + data_should_be_truncated = ( + integration.max_data_size and len(description) > integration.max_data_size + ) + if data_should_be_truncated: + description = description[: integration.max_data_size - len("...")] + "..." + + return description + + +def _set_db_data_on_span(span, connection_params): + # type: (Span, dict[str, Any]) -> None + span.set_data(SPANDATA.DB_SYSTEM, "redis") + + db = connection_params.get("db") + if db is not None: + span.set_data(SPANDATA.DB_NAME, str(db)) + + host = connection_params.get("host") + if host is not None: + span.set_data(SPANDATA.SERVER_ADDRESS, host) + + port = connection_params.get("port") + if port is not None: + span.set_data(SPANDATA.SERVER_PORT, port) + + +def _set_db_data(span, redis_instance): + # type: (Span, Redis[Any]) -> None + try: + _set_db_data_on_span(span, redis_instance.connection_pool.connection_kwargs) + except AttributeError: + pass # connections_kwargs may be missing in some cases diff --git a/aws/lambda_demo/sentry_sdk/integrations/redis/rb.py b/aws/lambda_demo/sentry_sdk/integrations/redis/rb.py new file mode 100644 index 000000000..1b3e2e530 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/redis/rb.py @@ -0,0 +1,32 @@ +""" +Instrumentation for Redis Blaster (rb) + +https://github.com/getsentry/rb +""" + +from sentry_sdk.integrations.redis._sync_common import patch_redis_client +from sentry_sdk.integrations.redis.modules.queries import _set_db_data + + +def _patch_rb(): + # type: () -> None + try: + import rb.clients # type: ignore + except ImportError: + pass + else: + patch_redis_client( + rb.clients.FanoutClient, + is_cluster=False, + set_db_data_fn=_set_db_data, + ) + patch_redis_client( + rb.clients.MappingClient, + is_cluster=False, + set_db_data_fn=_set_db_data, + ) + patch_redis_client( + rb.clients.RoutingClient, + is_cluster=False, + set_db_data_fn=_set_db_data, + ) diff --git a/aws/lambda_demo/sentry_sdk/integrations/redis/redis.py b/aws/lambda_demo/sentry_sdk/integrations/redis/redis.py new file mode 100644 index 000000000..c92958a32 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/redis/redis.py @@ -0,0 +1,69 @@ +""" +Instrumentation for Redis + +https://github.com/redis/redis-py +""" + +from sentry_sdk.integrations.redis._sync_common import ( + patch_redis_client, + patch_redis_pipeline, +) +from sentry_sdk.integrations.redis.modules.queries import _set_db_data + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Sequence + + +def _get_redis_command_args(command): + # type: (Any) -> Sequence[Any] + return command[0] + + +def _patch_redis(StrictRedis, client): # noqa: N803 + # type: (Any, Any) -> None + patch_redis_client( + StrictRedis, + is_cluster=False, + set_db_data_fn=_set_db_data, + ) + patch_redis_pipeline( + client.Pipeline, + is_cluster=False, + get_command_args_fn=_get_redis_command_args, + set_db_data_fn=_set_db_data, + ) + try: + strict_pipeline = client.StrictPipeline + except AttributeError: + pass + else: + patch_redis_pipeline( + strict_pipeline, + is_cluster=False, + get_command_args_fn=_get_redis_command_args, + set_db_data_fn=_set_db_data, + ) + + try: + import redis.asyncio + except ImportError: + pass + else: + from sentry_sdk.integrations.redis._async_common import ( + patch_redis_async_client, + patch_redis_async_pipeline, + ) + + patch_redis_async_client( + redis.asyncio.client.StrictRedis, + is_cluster=False, + set_db_data_fn=_set_db_data, + ) + patch_redis_async_pipeline( + redis.asyncio.client.Pipeline, + False, + _get_redis_command_args, + set_db_data_fn=_set_db_data, + ) diff --git a/aws/lambda_demo/sentry_sdk/integrations/redis/redis_cluster.py b/aws/lambda_demo/sentry_sdk/integrations/redis/redis_cluster.py new file mode 100644 index 000000000..80cdc7235 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/redis/redis_cluster.py @@ -0,0 +1,99 @@ +""" +Instrumentation for RedisCluster +This is part of the main redis-py client. + +https://github.com/redis/redis-py/blob/master/redis/cluster.py +""" + +from sentry_sdk.integrations.redis._sync_common import ( + patch_redis_client, + patch_redis_pipeline, +) +from sentry_sdk.integrations.redis.modules.queries import _set_db_data_on_span +from sentry_sdk.integrations.redis.utils import _parse_rediscluster_command + +from sentry_sdk.utils import capture_internal_exceptions + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from redis import RedisCluster + from redis.asyncio.cluster import ( + RedisCluster as AsyncRedisCluster, + ClusterPipeline as AsyncClusterPipeline, + ) + from sentry_sdk.tracing import Span + + +def _set_async_cluster_db_data(span, async_redis_cluster_instance): + # type: (Span, AsyncRedisCluster[Any]) -> None + default_node = async_redis_cluster_instance.get_default_node() + if default_node is not None and default_node.connection_kwargs is not None: + _set_db_data_on_span(span, default_node.connection_kwargs) + + +def _set_async_cluster_pipeline_db_data(span, async_redis_cluster_pipeline_instance): + # type: (Span, AsyncClusterPipeline[Any]) -> None + with capture_internal_exceptions(): + _set_async_cluster_db_data( + span, + # the AsyncClusterPipeline has always had a `_client` attr but it is private so potentially problematic and mypy + # does not recognize it - see https://github.com/redis/redis-py/blame/v5.0.0/redis/asyncio/cluster.py#L1386 + async_redis_cluster_pipeline_instance._client, # type: ignore[attr-defined] + ) + + +def _set_cluster_db_data(span, redis_cluster_instance): + # type: (Span, RedisCluster[Any]) -> None + default_node = redis_cluster_instance.get_default_node() + + if default_node is not None: + connection_params = { + "host": default_node.host, + "port": default_node.port, + } + _set_db_data_on_span(span, connection_params) + + +def _patch_redis_cluster(): + # type: () -> None + """Patches the cluster module on redis SDK (as opposed to rediscluster library)""" + try: + from redis import RedisCluster, cluster + except ImportError: + pass + else: + patch_redis_client( + RedisCluster, + is_cluster=True, + set_db_data_fn=_set_cluster_db_data, + ) + patch_redis_pipeline( + cluster.ClusterPipeline, + is_cluster=True, + get_command_args_fn=_parse_rediscluster_command, + set_db_data_fn=_set_cluster_db_data, + ) + + try: + from redis.asyncio import cluster as async_cluster + except ImportError: + pass + else: + from sentry_sdk.integrations.redis._async_common import ( + patch_redis_async_client, + patch_redis_async_pipeline, + ) + + patch_redis_async_client( + async_cluster.RedisCluster, + is_cluster=True, + set_db_data_fn=_set_async_cluster_db_data, + ) + patch_redis_async_pipeline( + async_cluster.ClusterPipeline, + is_cluster=True, + get_command_args_fn=_parse_rediscluster_command, + set_db_data_fn=_set_async_cluster_pipeline_db_data, + ) diff --git a/aws/lambda_demo/sentry_sdk/integrations/redis/redis_py_cluster_legacy.py b/aws/lambda_demo/sentry_sdk/integrations/redis/redis_py_cluster_legacy.py new file mode 100644 index 000000000..ad1c23633 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/redis/redis_py_cluster_legacy.py @@ -0,0 +1,50 @@ +""" +Instrumentation for redis-py-cluster +The project redis-py-cluster is EOL and was integrated into redis-py starting from version 4.1.0 (Dec 26, 2021). + +https://github.com/grokzen/redis-py-cluster +""" + +from sentry_sdk.integrations.redis._sync_common import ( + patch_redis_client, + patch_redis_pipeline, +) +from sentry_sdk.integrations.redis.modules.queries import _set_db_data +from sentry_sdk.integrations.redis.utils import _parse_rediscluster_command + + +def _patch_rediscluster(): + # type: () -> None + try: + import rediscluster # type: ignore + except ImportError: + return + + patch_redis_client( + rediscluster.RedisCluster, + is_cluster=True, + set_db_data_fn=_set_db_data, + ) + + # up to v1.3.6, __version__ attribute is a tuple + # from v2.0.0, __version__ is a string and VERSION a tuple + version = getattr(rediscluster, "VERSION", rediscluster.__version__) + + # StrictRedisCluster was introduced in v0.2.0 and removed in v2.0.0 + # https://github.com/Grokzen/redis-py-cluster/blob/master/docs/release-notes.rst + if (0, 2, 0) < version < (2, 0, 0): + pipeline_cls = rediscluster.pipeline.StrictClusterPipeline + patch_redis_client( + rediscluster.StrictRedisCluster, + is_cluster=True, + set_db_data_fn=_set_db_data, + ) + else: + pipeline_cls = rediscluster.pipeline.ClusterPipeline + + patch_redis_pipeline( + pipeline_cls, + is_cluster=True, + get_command_args_fn=_parse_rediscluster_command, + set_db_data_fn=_set_db_data, + ) diff --git a/aws/lambda_demo/sentry_sdk/integrations/redis/utils.py b/aws/lambda_demo/sentry_sdk/integrations/redis/utils.py new file mode 100644 index 000000000..27fae1e8c --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/redis/utils.py @@ -0,0 +1,144 @@ +from sentry_sdk.consts import SPANDATA +from sentry_sdk.integrations.redis.consts import ( + _COMMANDS_INCLUDING_SENSITIVE_DATA, + _MAX_NUM_ARGS, + _MAX_NUM_COMMANDS, + _MULTI_KEY_COMMANDS, + _SINGLE_KEY_COMMANDS, +) +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Optional, Sequence + from sentry_sdk.tracing import Span + + +def _get_safe_command(name, args): + # type: (str, Sequence[Any]) -> str + command_parts = [name] + + for i, arg in enumerate(args): + if i > _MAX_NUM_ARGS: + break + + name_low = name.lower() + + if name_low in _COMMANDS_INCLUDING_SENSITIVE_DATA: + command_parts.append(SENSITIVE_DATA_SUBSTITUTE) + continue + + arg_is_the_key = i == 0 + if arg_is_the_key: + command_parts.append(repr(arg)) + + else: + if should_send_default_pii(): + command_parts.append(repr(arg)) + else: + command_parts.append(SENSITIVE_DATA_SUBSTITUTE) + + command = " ".join(command_parts) + return command + + +def _safe_decode(key): + # type: (Any) -> str + if isinstance(key, bytes): + try: + return key.decode() + except UnicodeDecodeError: + return "" + + return str(key) + + +def _key_as_string(key): + # type: (Any) -> str + if isinstance(key, (dict, list, tuple)): + key = ", ".join(_safe_decode(x) for x in key) + elif isinstance(key, bytes): + key = _safe_decode(key) + elif key is None: + key = "" + else: + key = str(key) + + return key + + +def _get_safe_key(method_name, args, kwargs): + # type: (str, Optional[tuple[Any, ...]], Optional[dict[str, Any]]) -> Optional[tuple[str, ...]] + """ + Gets the key (or keys) from the given method_name. + The method_name could be a redis command or a django caching command + """ + key = None + + if args is not None and method_name.lower() in _MULTI_KEY_COMMANDS: + # for example redis "mget" + key = tuple(args) + + elif args is not None and len(args) >= 1: + # for example django "set_many/get_many" or redis "get" + if isinstance(args[0], (dict, list, tuple)): + key = tuple(args[0]) + else: + key = (args[0],) + + elif kwargs is not None and "key" in kwargs: + # this is a legacy case for older versions of Django + if isinstance(kwargs["key"], (list, tuple)): + if len(kwargs["key"]) > 0: + key = tuple(kwargs["key"]) + else: + if kwargs["key"] is not None: + key = (kwargs["key"],) + + return key + + +def _parse_rediscluster_command(command): + # type: (Any) -> Sequence[Any] + return command.args + + +def _set_pipeline_data( + span, is_cluster, get_command_args_fn, is_transaction, command_stack +): + # type: (Span, bool, Any, bool, Sequence[Any]) -> None + span.set_tag("redis.is_cluster", is_cluster) + span.set_tag("redis.transaction", is_transaction) + + commands = [] + for i, arg in enumerate(command_stack): + if i >= _MAX_NUM_COMMANDS: + break + + command = get_command_args_fn(arg) + commands.append(_get_safe_command(command[0], command[1:])) + + span.set_data( + "redis.commands", + { + "count": len(command_stack), + "first_ten": commands, + }, + ) + + +def _set_client_data(span, is_cluster, name, *args): + # type: (Span, bool, str, *Any) -> None + span.set_tag("redis.is_cluster", is_cluster) + if name: + span.set_tag("redis.command", name) + span.set_tag(SPANDATA.DB_OPERATION, name) + + if name and args: + name_low = name.lower() + if (name_low in _SINGLE_KEY_COMMANDS) or ( + name_low in _MULTI_KEY_COMMANDS and len(args) == 1 + ): + span.set_tag("redis.key", args[0]) diff --git a/aws/lambda_demo/sentry_sdk/integrations/rq.py b/aws/lambda_demo/sentry_sdk/integrations/rq.py new file mode 100644 index 000000000..d4fca6a33 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/rq.py @@ -0,0 +1,161 @@ +import weakref + +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.api import continue_trace +from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration +from sentry_sdk.integrations.logging import ignore_logger +from sentry_sdk.tracing import TRANSACTION_SOURCE_TASK +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + format_timestamp, + parse_version, +) + +try: + from rq.queue import Queue + from rq.timeouts import JobTimeoutException + from rq.version import VERSION as RQ_VERSION + from rq.worker import Worker + from rq.job import JobStatus +except ImportError: + raise DidNotEnable("RQ not installed") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Callable + + from sentry_sdk._types import Event, EventProcessor + from sentry_sdk.utils import ExcInfo + + from rq.job import Job + + +class RqIntegration(Integration): + identifier = "rq" + origin = f"auto.queue.{identifier}" + + @staticmethod + def setup_once(): + # type: () -> None + version = parse_version(RQ_VERSION) + _check_minimum_version(RqIntegration, version) + + old_perform_job = Worker.perform_job + + @ensure_integration_enabled(RqIntegration, old_perform_job) + def sentry_patched_perform_job(self, job, *args, **kwargs): + # type: (Any, Job, *Queue, **Any) -> bool + with sentry_sdk.new_scope() as scope: + scope.clear_breadcrumbs() + scope.add_event_processor(_make_event_processor(weakref.ref(job))) + + transaction = continue_trace( + job.meta.get("_sentry_trace_headers") or {}, + op=OP.QUEUE_TASK_RQ, + name="unknown RQ task", + source=TRANSACTION_SOURCE_TASK, + origin=RqIntegration.origin, + ) + + with capture_internal_exceptions(): + transaction.name = job.func_name + + with sentry_sdk.start_transaction( + transaction, + custom_sampling_context={"rq_job": job}, + ): + rv = old_perform_job(self, job, *args, **kwargs) + + if self.is_horse: + # We're inside of a forked process and RQ is + # about to call `os._exit`. Make sure that our + # events get sent out. + sentry_sdk.get_client().flush() + + return rv + + Worker.perform_job = sentry_patched_perform_job + + old_handle_exception = Worker.handle_exception + + def sentry_patched_handle_exception(self, job, *exc_info, **kwargs): + # type: (Worker, Any, *Any, **Any) -> Any + retry = ( + hasattr(job, "retries_left") + and job.retries_left + and job.retries_left > 0 + ) + failed = job._status == JobStatus.FAILED or job.is_failed + if failed and not retry: + _capture_exception(exc_info) + + return old_handle_exception(self, job, *exc_info, **kwargs) + + Worker.handle_exception = sentry_patched_handle_exception + + old_enqueue_job = Queue.enqueue_job + + @ensure_integration_enabled(RqIntegration, old_enqueue_job) + def sentry_patched_enqueue_job(self, job, **kwargs): + # type: (Queue, Any, **Any) -> Any + scope = sentry_sdk.get_current_scope() + if scope.span is not None: + job.meta["_sentry_trace_headers"] = dict( + scope.iter_trace_propagation_headers() + ) + + return old_enqueue_job(self, job, **kwargs) + + Queue.enqueue_job = sentry_patched_enqueue_job + + ignore_logger("rq.worker") + + +def _make_event_processor(weak_job): + # type: (Callable[[], Job]) -> EventProcessor + def event_processor(event, hint): + # type: (Event, dict[str, Any]) -> Event + job = weak_job() + if job is not None: + with capture_internal_exceptions(): + extra = event.setdefault("extra", {}) + rq_job = { + "job_id": job.id, + "func": job.func_name, + "args": job.args, + "kwargs": job.kwargs, + "description": job.description, + } + + if job.enqueued_at: + rq_job["enqueued_at"] = format_timestamp(job.enqueued_at) + if job.started_at: + rq_job["started_at"] = format_timestamp(job.started_at) + + extra["rq-job"] = rq_job + + if "exc_info" in hint: + with capture_internal_exceptions(): + if issubclass(hint["exc_info"][0], JobTimeoutException): + event["fingerprint"] = ["rq", "JobTimeoutException", job.func_name] + + return event + + return event_processor + + +def _capture_exception(exc_info, **kwargs): + # type: (ExcInfo, **Any) -> None + client = sentry_sdk.get_client() + + event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "rq", "handled": False}, + ) + + sentry_sdk.capture_event(event, hint=hint) diff --git a/aws/lambda_demo/sentry_sdk/integrations/rust_tracing.py b/aws/lambda_demo/sentry_sdk/integrations/rust_tracing.py new file mode 100644 index 000000000..e4c211814 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/rust_tracing.py @@ -0,0 +1,284 @@ +""" +This integration ingests tracing data from native extensions written in Rust. + +Using it requires additional setup on the Rust side to accept a +`RustTracingLayer` Python object and register it with the `tracing-subscriber` +using an adapter from the `pyo3-python-tracing-subscriber` crate. For example: +```rust +#[pyfunction] +pub fn initialize_tracing(py_impl: Bound<'_, PyAny>) { + tracing_subscriber::registry() + .with(pyo3_python_tracing_subscriber::PythonCallbackLayerBridge::new(py_impl)) + .init(); +} +``` + +Usage in Python would then look like: +``` +sentry_sdk.init( + dsn=sentry_dsn, + integrations=[ + RustTracingIntegration( + "demo_rust_extension", + demo_rust_extension.initialize_tracing, + event_type_mapping=event_type_mapping, + ) + ], +) +``` + +Each native extension requires its own integration. +""" + +import json +from enum import Enum, auto +from typing import Any, Callable, Dict, Tuple, Optional + +import sentry_sdk +from sentry_sdk.integrations import Integration +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import Span as SentrySpan +from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE + +TraceState = Optional[Tuple[Optional[SentrySpan], SentrySpan]] + + +class RustTracingLevel(Enum): + Trace = "TRACE" + Debug = "DEBUG" + Info = "INFO" + Warn = "WARN" + Error = "ERROR" + + +class EventTypeMapping(Enum): + Ignore = auto() + Exc = auto() + Breadcrumb = auto() + Event = auto() + + +def tracing_level_to_sentry_level(level): + # type: (str) -> sentry_sdk._types.LogLevelStr + level = RustTracingLevel(level) + if level in (RustTracingLevel.Trace, RustTracingLevel.Debug): + return "debug" + elif level == RustTracingLevel.Info: + return "info" + elif level == RustTracingLevel.Warn: + return "warning" + elif level == RustTracingLevel.Error: + return "error" + else: + # Better this than crashing + return "info" + + +def extract_contexts(event: Dict[str, Any]) -> Dict[str, Any]: + metadata = event.get("metadata", {}) + contexts = {} + + location = {} + for field in ["module_path", "file", "line"]: + if field in metadata: + location[field] = metadata[field] + if len(location) > 0: + contexts["rust_tracing_location"] = location + + fields = {} + for field in metadata.get("fields", []): + fields[field] = event.get(field) + if len(fields) > 0: + contexts["rust_tracing_fields"] = fields + + return contexts + + +def process_event(event: Dict[str, Any]) -> None: + metadata = event.get("metadata", {}) + + logger = metadata.get("target") + level = tracing_level_to_sentry_level(metadata.get("level")) + message = event.get("message") # type: sentry_sdk._types.Any + contexts = extract_contexts(event) + + sentry_event = { + "logger": logger, + "level": level, + "message": message, + "contexts": contexts, + } # type: sentry_sdk._types.Event + + sentry_sdk.capture_event(sentry_event) + + +def process_exception(event: Dict[str, Any]) -> None: + process_event(event) + + +def process_breadcrumb(event: Dict[str, Any]) -> None: + level = tracing_level_to_sentry_level(event.get("metadata", {}).get("level")) + message = event.get("message") + + sentry_sdk.add_breadcrumb(level=level, message=message) + + +def default_span_filter(metadata: Dict[str, Any]) -> bool: + return RustTracingLevel(metadata.get("level")) in ( + RustTracingLevel.Error, + RustTracingLevel.Warn, + RustTracingLevel.Info, + ) + + +def default_event_type_mapping(metadata: Dict[str, Any]) -> EventTypeMapping: + level = RustTracingLevel(metadata.get("level")) + if level == RustTracingLevel.Error: + return EventTypeMapping.Exc + elif level in (RustTracingLevel.Warn, RustTracingLevel.Info): + return EventTypeMapping.Breadcrumb + elif level in (RustTracingLevel.Debug, RustTracingLevel.Trace): + return EventTypeMapping.Ignore + else: + return EventTypeMapping.Ignore + + +class RustTracingLayer: + def __init__( + self, + origin: str, + event_type_mapping: Callable[ + [Dict[str, Any]], EventTypeMapping + ] = default_event_type_mapping, + span_filter: Callable[[Dict[str, Any]], bool] = default_span_filter, + include_tracing_fields: Optional[bool] = None, + ): + self.origin = origin + self.event_type_mapping = event_type_mapping + self.span_filter = span_filter + self.include_tracing_fields = include_tracing_fields + + def _include_tracing_fields(self) -> bool: + """ + By default, the values of tracing fields are not included in case they + contain PII. A user may override that by passing `True` for the + `include_tracing_fields` keyword argument of this integration or by + setting `send_default_pii` to `True` in their Sentry client options. + """ + return ( + should_send_default_pii() + if self.include_tracing_fields is None + else self.include_tracing_fields + ) + + def on_event(self, event: str, _span_state: TraceState) -> None: + deserialized_event = json.loads(event) + metadata = deserialized_event.get("metadata", {}) + + event_type = self.event_type_mapping(metadata) + if event_type == EventTypeMapping.Ignore: + return + elif event_type == EventTypeMapping.Exc: + process_exception(deserialized_event) + elif event_type == EventTypeMapping.Breadcrumb: + process_breadcrumb(deserialized_event) + elif event_type == EventTypeMapping.Event: + process_event(deserialized_event) + + def on_new_span(self, attrs: str, span_id: str) -> TraceState: + attrs = json.loads(attrs) + metadata = attrs.get("metadata", {}) + + if not self.span_filter(metadata): + return None + + module_path = metadata.get("module_path") + name = metadata.get("name") + message = attrs.get("message") + + if message is not None: + sentry_span_name = message + elif module_path is not None and name is not None: + sentry_span_name = f"{module_path}::{name}" # noqa: E231 + elif name is not None: + sentry_span_name = name + else: + sentry_span_name = "" + + kwargs = { + "op": "function", + "name": sentry_span_name, + "origin": self.origin, + } + + scope = sentry_sdk.get_current_scope() + parent_sentry_span = scope.span + if parent_sentry_span: + sentry_span = parent_sentry_span.start_child(**kwargs) + else: + sentry_span = scope.start_span(**kwargs) + + fields = metadata.get("fields", []) + for field in fields: + if self._include_tracing_fields(): + sentry_span.set_data(field, attrs.get(field)) + else: + sentry_span.set_data(field, SENSITIVE_DATA_SUBSTITUTE) + + scope.span = sentry_span + return (parent_sentry_span, sentry_span) + + def on_close(self, span_id: str, span_state: TraceState) -> None: + if span_state is None: + return + + parent_sentry_span, sentry_span = span_state + sentry_span.finish() + sentry_sdk.get_current_scope().span = parent_sentry_span + + def on_record(self, span_id: str, values: str, span_state: TraceState) -> None: + if span_state is None: + return + _parent_sentry_span, sentry_span = span_state + + deserialized_values = json.loads(values) + for key, value in deserialized_values.items(): + if self._include_tracing_fields(): + sentry_span.set_data(key, value) + else: + sentry_span.set_data(key, SENSITIVE_DATA_SUBSTITUTE) + + +class RustTracingIntegration(Integration): + """ + Ingests tracing data from a Rust native extension's `tracing` instrumentation. + + If a project uses more than one Rust native extension, each one will need + its own instance of `RustTracingIntegration` with an initializer function + specific to that extension. + + Since all of the setup for this integration requires instance-specific state + which is not available in `setup_once()`, setup instead happens in `__init__()`. + """ + + def __init__( + self, + identifier: str, + initializer: Callable[[RustTracingLayer], None], + event_type_mapping: Callable[ + [Dict[str, Any]], EventTypeMapping + ] = default_event_type_mapping, + span_filter: Callable[[Dict[str, Any]], bool] = default_span_filter, + include_tracing_fields: Optional[bool] = None, + ): + self.identifier = identifier + origin = f"auto.function.rust_tracing.{identifier}" + self.tracing_layer = RustTracingLayer( + origin, event_type_mapping, span_filter, include_tracing_fields + ) + + initializer(self.tracing_layer) + + @staticmethod + def setup_once() -> None: + pass diff --git a/aws/lambda_demo/sentry_sdk/integrations/sanic.py b/aws/lambda_demo/sentry_sdk/integrations/sanic.py new file mode 100644 index 000000000..dfcc299d4 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/sanic.py @@ -0,0 +1,368 @@ +import sys +import weakref +from inspect import isawaitable +from urllib.parse import urlsplit + +import sentry_sdk +from sentry_sdk import continue_trace +from sentry_sdk.consts import OP +from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable +from sentry_sdk.integrations._wsgi_common import RequestExtractor, _filter_headers +from sentry_sdk.integrations.logging import ignore_logger +from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT, TRANSACTION_SOURCE_URL +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + HAS_REAL_CONTEXTVARS, + CONTEXTVARS_ERROR_MESSAGE, + parse_version, + reraise, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Container + from typing import Any + from typing import Callable + from typing import Optional + from typing import Union + from typing import Dict + + from sanic.request import Request, RequestParameters + from sanic.response import BaseHTTPResponse + + from sentry_sdk._types import Event, EventProcessor, ExcInfo, Hint + from sanic.router import Route + +try: + from sanic import Sanic, __version__ as SANIC_VERSION + from sanic.exceptions import SanicException + from sanic.router import Router + from sanic.handlers import ErrorHandler +except ImportError: + raise DidNotEnable("Sanic not installed") + +old_error_handler_lookup = ErrorHandler.lookup +old_handle_request = Sanic.handle_request +old_router_get = Router.get + +try: + # This method was introduced in Sanic v21.9 + old_startup = Sanic._startup +except AttributeError: + pass + + +class SanicIntegration(Integration): + identifier = "sanic" + origin = f"auto.http.{identifier}" + version = None + + def __init__(self, unsampled_statuses=frozenset({404})): + # type: (Optional[Container[int]]) -> None + """ + The unsampled_statuses parameter can be used to specify for which HTTP statuses the + transactions should not be sent to Sentry. By default, transactions are sent for all + HTTP statuses, except 404. Set unsampled_statuses to None to send transactions for all + HTTP statuses, including 404. + """ + self._unsampled_statuses = unsampled_statuses or set() + + @staticmethod + def setup_once(): + # type: () -> None + SanicIntegration.version = parse_version(SANIC_VERSION) + _check_minimum_version(SanicIntegration, SanicIntegration.version) + + if not HAS_REAL_CONTEXTVARS: + # We better have contextvars or we're going to leak state between + # requests. + raise DidNotEnable( + "The sanic integration for Sentry requires Python 3.7+ " + " or the aiocontextvars package." + CONTEXTVARS_ERROR_MESSAGE + ) + + if SANIC_VERSION.startswith("0.8."): + # Sanic 0.8 and older creates a logger named "root" and puts a + # stringified version of every exception in there (without exc_info), + # which our error deduplication can't detect. + # + # We explicitly check the version here because it is a very + # invasive step to ignore this logger and not necessary in newer + # versions at all. + # + # https://github.com/huge-success/sanic/issues/1332 + ignore_logger("root") + + if SanicIntegration.version is not None and SanicIntegration.version < (21, 9): + _setup_legacy_sanic() + return + + _setup_sanic() + + +class SanicRequestExtractor(RequestExtractor): + def content_length(self): + # type: () -> int + if self.request.body is None: + return 0 + return len(self.request.body) + + def cookies(self): + # type: () -> Dict[str, str] + return dict(self.request.cookies) + + def raw_data(self): + # type: () -> bytes + return self.request.body + + def form(self): + # type: () -> RequestParameters + return self.request.form + + def is_json(self): + # type: () -> bool + raise NotImplementedError() + + def json(self): + # type: () -> Optional[Any] + return self.request.json + + def files(self): + # type: () -> RequestParameters + return self.request.files + + def size_of_file(self, file): + # type: (Any) -> int + return len(file.body or ()) + + +def _setup_sanic(): + # type: () -> None + Sanic._startup = _startup + ErrorHandler.lookup = _sentry_error_handler_lookup + + +def _setup_legacy_sanic(): + # type: () -> None + Sanic.handle_request = _legacy_handle_request + Router.get = _legacy_router_get + ErrorHandler.lookup = _sentry_error_handler_lookup + + +async def _startup(self): + # type: (Sanic) -> None + # This happens about as early in the lifecycle as possible, just after the + # Request object is created. The body has not yet been consumed. + self.signal("http.lifecycle.request")(_context_enter) + + # This happens after the handler is complete. In v21.9 this signal is not + # dispatched when there is an exception. Therefore we need to close out + # and call _context_exit from the custom exception handler as well. + # See https://github.com/sanic-org/sanic/issues/2297 + self.signal("http.lifecycle.response")(_context_exit) + + # This happens inside of request handling immediately after the route + # has been identified by the router. + self.signal("http.routing.after")(_set_transaction) + + # The above signals need to be declared before this can be called. + await old_startup(self) + + +async def _context_enter(request): + # type: (Request) -> None + request.ctx._sentry_do_integration = ( + sentry_sdk.get_client().get_integration(SanicIntegration) is not None + ) + + if not request.ctx._sentry_do_integration: + return + + weak_request = weakref.ref(request) + request.ctx._sentry_scope = sentry_sdk.isolation_scope() + scope = request.ctx._sentry_scope.__enter__() + scope.clear_breadcrumbs() + scope.add_event_processor(_make_request_processor(weak_request)) + + transaction = continue_trace( + dict(request.headers), + op=OP.HTTP_SERVER, + # Unless the request results in a 404 error, the name and source will get overwritten in _set_transaction + name=request.path, + source=TRANSACTION_SOURCE_URL, + origin=SanicIntegration.origin, + ) + request.ctx._sentry_transaction = sentry_sdk.start_transaction( + transaction + ).__enter__() + + +async def _context_exit(request, response=None): + # type: (Request, Optional[BaseHTTPResponse]) -> None + with capture_internal_exceptions(): + if not request.ctx._sentry_do_integration: + return + + integration = sentry_sdk.get_client().get_integration(SanicIntegration) + + response_status = None if response is None else response.status + + # This capture_internal_exceptions block has been intentionally nested here, so that in case an exception + # happens while trying to end the transaction, we still attempt to exit the hub. + with capture_internal_exceptions(): + request.ctx._sentry_transaction.set_http_status(response_status) + request.ctx._sentry_transaction.sampled &= ( + isinstance(integration, SanicIntegration) + and response_status not in integration._unsampled_statuses + ) + request.ctx._sentry_transaction.__exit__(None, None, None) + + request.ctx._sentry_scope.__exit__(None, None, None) + + +async def _set_transaction(request, route, **_): + # type: (Request, Route, **Any) -> None + if request.ctx._sentry_do_integration: + with capture_internal_exceptions(): + scope = sentry_sdk.get_current_scope() + route_name = route.name.replace(request.app.name, "").strip(".") + scope.set_transaction_name(route_name, source=TRANSACTION_SOURCE_COMPONENT) + + +def _sentry_error_handler_lookup(self, exception, *args, **kwargs): + # type: (Any, Exception, *Any, **Any) -> Optional[object] + _capture_exception(exception) + old_error_handler = old_error_handler_lookup(self, exception, *args, **kwargs) + + if old_error_handler is None: + return None + + if sentry_sdk.get_client().get_integration(SanicIntegration) is None: + return old_error_handler + + async def sentry_wrapped_error_handler(request, exception): + # type: (Request, Exception) -> Any + try: + response = old_error_handler(request, exception) + if isawaitable(response): + response = await response + return response + except Exception: + # Report errors that occur in Sanic error handler. These + # exceptions will not even show up in Sanic's + # `sanic.exceptions` logger. + exc_info = sys.exc_info() + _capture_exception(exc_info) + reraise(*exc_info) + finally: + # As mentioned in previous comment in _startup, this can be removed + # after https://github.com/sanic-org/sanic/issues/2297 is resolved + if SanicIntegration.version and SanicIntegration.version == (21, 9): + await _context_exit(request) + + return sentry_wrapped_error_handler + + +async def _legacy_handle_request(self, request, *args, **kwargs): + # type: (Any, Request, *Any, **Any) -> Any + if sentry_sdk.get_client().get_integration(SanicIntegration) is None: + return await old_handle_request(self, request, *args, **kwargs) + + weak_request = weakref.ref(request) + + with sentry_sdk.isolation_scope() as scope: + scope.clear_breadcrumbs() + scope.add_event_processor(_make_request_processor(weak_request)) + + response = old_handle_request(self, request, *args, **kwargs) + if isawaitable(response): + response = await response + + return response + + +def _legacy_router_get(self, *args): + # type: (Any, Union[Any, Request]) -> Any + rv = old_router_get(self, *args) + if sentry_sdk.get_client().get_integration(SanicIntegration) is not None: + with capture_internal_exceptions(): + scope = sentry_sdk.get_isolation_scope() + if SanicIntegration.version and SanicIntegration.version >= (21, 3): + # Sanic versions above and including 21.3 append the app name to the + # route name, and so we need to remove it from Route name so the + # transaction name is consistent across all versions + sanic_app_name = self.ctx.app.name + sanic_route = rv[0].name + + if sanic_route.startswith("%s." % sanic_app_name): + # We add a 1 to the len of the sanic_app_name because there is a dot + # that joins app name and the route name + # Format: app_name.route_name + sanic_route = sanic_route[len(sanic_app_name) + 1 :] + + scope.set_transaction_name( + sanic_route, source=TRANSACTION_SOURCE_COMPONENT + ) + else: + scope.set_transaction_name( + rv[0].__name__, source=TRANSACTION_SOURCE_COMPONENT + ) + + return rv + + +@ensure_integration_enabled(SanicIntegration) +def _capture_exception(exception): + # type: (Union[ExcInfo, BaseException]) -> None + with capture_internal_exceptions(): + event, hint = event_from_exception( + exception, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "sanic", "handled": False}, + ) + + if hint and hasattr(hint["exc_info"][0], "quiet") and hint["exc_info"][0].quiet: + return + + sentry_sdk.capture_event(event, hint=hint) + + +def _make_request_processor(weak_request): + # type: (Callable[[], Request]) -> EventProcessor + def sanic_processor(event, hint): + # type: (Event, Optional[Hint]) -> Optional[Event] + + try: + if hint and issubclass(hint["exc_info"][0], SanicException): + return None + except KeyError: + pass + + request = weak_request() + if request is None: + return event + + with capture_internal_exceptions(): + extractor = SanicRequestExtractor(request) + extractor.extract_into_event(event) + + request_info = event["request"] + urlparts = urlsplit(request.url) + + request_info["url"] = "%s://%s%s" % ( + urlparts.scheme, + urlparts.netloc, + urlparts.path, + ) + + request_info["query_string"] = urlparts.query + request_info["method"] = request.method + request_info["env"] = {"REMOTE_ADDR": request.remote_addr} + request_info["headers"] = _filter_headers(dict(request.headers)) + + return event + + return sanic_processor diff --git a/aws/lambda_demo/sentry_sdk/integrations/serverless.py b/aws/lambda_demo/sentry_sdk/integrations/serverless.py new file mode 100644 index 000000000..760c07ffa --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/serverless.py @@ -0,0 +1,76 @@ +import sys +from functools import wraps + +import sentry_sdk +from sentry_sdk.utils import event_from_exception, reraise + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import TypeVar + from typing import Union + from typing import Optional + from typing import overload + + F = TypeVar("F", bound=Callable[..., Any]) + +else: + + def overload(x): + # type: (F) -> F + return x + + +@overload +def serverless_function(f, flush=True): + # type: (F, bool) -> F + pass + + +@overload +def serverless_function(f=None, flush=True): # noqa: F811 + # type: (None, bool) -> Callable[[F], F] + pass + + +def serverless_function(f=None, flush=True): # noqa + # type: (Optional[F], bool) -> Union[F, Callable[[F], F]] + def wrapper(f): + # type: (F) -> F + @wraps(f) + def inner(*args, **kwargs): + # type: (*Any, **Any) -> Any + with sentry_sdk.isolation_scope() as scope: + scope.clear_breadcrumbs() + + try: + return f(*args, **kwargs) + except Exception: + _capture_and_reraise() + finally: + if flush: + sentry_sdk.flush() + + return inner # type: ignore + + if f is None: + return wrapper + else: + return wrapper(f) + + +def _capture_and_reraise(): + # type: () -> None + exc_info = sys.exc_info() + client = sentry_sdk.get_client() + if client.is_active(): + event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "serverless", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + reraise(*exc_info) diff --git a/aws/lambda_demo/sentry_sdk/integrations/socket.py b/aws/lambda_demo/sentry_sdk/integrations/socket.py new file mode 100644 index 000000000..0866ceb60 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/socket.py @@ -0,0 +1,92 @@ +import socket + +import sentry_sdk +from sentry_sdk._types import MYPY +from sentry_sdk.consts import OP +from sentry_sdk.integrations import Integration + +if MYPY: + from socket import AddressFamily, SocketKind + from typing import Tuple, Optional, Union, List + +__all__ = ["SocketIntegration"] + + +class SocketIntegration(Integration): + identifier = "socket" + origin = f"auto.socket.{identifier}" + + @staticmethod + def setup_once(): + # type: () -> None + """ + patches two of the most used functions of socket: create_connection and getaddrinfo(dns resolver) + """ + _patch_create_connection() + _patch_getaddrinfo() + + +def _get_span_description(host, port): + # type: (Union[bytes, str, None], Union[str, int, None]) -> str + + try: + host = host.decode() # type: ignore + except (UnicodeDecodeError, AttributeError): + pass + + description = "%s:%s" % (host, port) # type: ignore + + return description + + +def _patch_create_connection(): + # type: () -> None + real_create_connection = socket.create_connection + + def create_connection( + address, + timeout=socket._GLOBAL_DEFAULT_TIMEOUT, # type: ignore + source_address=None, + ): + # type: (Tuple[Optional[str], int], Optional[float], Optional[Tuple[Union[bytearray, bytes, str], int]])-> socket.socket + integration = sentry_sdk.get_client().get_integration(SocketIntegration) + if integration is None: + return real_create_connection(address, timeout, source_address) + + with sentry_sdk.start_span( + op=OP.SOCKET_CONNECTION, + name=_get_span_description(address[0], address[1]), + origin=SocketIntegration.origin, + ) as span: + span.set_data("address", address) + span.set_data("timeout", timeout) + span.set_data("source_address", source_address) + + return real_create_connection( + address=address, timeout=timeout, source_address=source_address + ) + + socket.create_connection = create_connection # type: ignore + + +def _patch_getaddrinfo(): + # type: () -> None + real_getaddrinfo = socket.getaddrinfo + + def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0): + # type: (Union[bytes, str, None], Union[str, int, None], int, int, int, int) -> List[Tuple[AddressFamily, SocketKind, int, str, Union[Tuple[str, int], Tuple[str, int, int, int]]]] + integration = sentry_sdk.get_client().get_integration(SocketIntegration) + if integration is None: + return real_getaddrinfo(host, port, family, type, proto, flags) + + with sentry_sdk.start_span( + op=OP.SOCKET_DNS, + name=_get_span_description(host, port), + origin=SocketIntegration.origin, + ) as span: + span.set_data("host", host) + span.set_data("port", port) + + return real_getaddrinfo(host, port, family, type, proto, flags) + + socket.getaddrinfo = getaddrinfo # type: ignore diff --git a/aws/lambda_demo/sentry_sdk/integrations/spark/__init__.py b/aws/lambda_demo/sentry_sdk/integrations/spark/__init__.py new file mode 100644 index 000000000..10d94163c --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/spark/__init__.py @@ -0,0 +1,4 @@ +from sentry_sdk.integrations.spark.spark_driver import SparkIntegration +from sentry_sdk.integrations.spark.spark_worker import SparkWorkerIntegration + +__all__ = ["SparkIntegration", "SparkWorkerIntegration"] diff --git a/aws/lambda_demo/sentry_sdk/integrations/spark/spark_driver.py b/aws/lambda_demo/sentry_sdk/integrations/spark/spark_driver.py new file mode 100644 index 000000000..a86f16344 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/spark/spark_driver.py @@ -0,0 +1,285 @@ +import sentry_sdk +from sentry_sdk.integrations import Integration +from sentry_sdk.utils import capture_internal_exceptions, ensure_integration_enabled + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Optional + + from sentry_sdk._types import Event, Hint + from pyspark import SparkContext + + +class SparkIntegration(Integration): + identifier = "spark" + + @staticmethod + def setup_once(): + # type: () -> None + _setup_sentry_tracing() + + +def _set_app_properties(): + # type: () -> None + """ + Set properties in driver that propagate to worker processes, allowing for workers to have access to those properties. + This allows worker integration to have access to app_name and application_id. + """ + from pyspark import SparkContext + + spark_context = SparkContext._active_spark_context + if spark_context: + spark_context.setLocalProperty("sentry_app_name", spark_context.appName) + spark_context.setLocalProperty( + "sentry_application_id", spark_context.applicationId + ) + + +def _start_sentry_listener(sc): + # type: (SparkContext) -> None + """ + Start java gateway server to add custom `SparkListener` + """ + from pyspark.java_gateway import ensure_callback_server_started + + gw = sc._gateway + ensure_callback_server_started(gw) + listener = SentryListener() + sc._jsc.sc().addSparkListener(listener) + + +def _add_event_processor(sc): + # type: (SparkContext) -> None + scope = sentry_sdk.get_isolation_scope() + + @scope.add_event_processor + def process_event(event, hint): + # type: (Event, Hint) -> Optional[Event] + with capture_internal_exceptions(): + if sentry_sdk.get_client().get_integration(SparkIntegration) is None: + return event + + if sc._active_spark_context is None: + return event + + event.setdefault("user", {}).setdefault("id", sc.sparkUser()) + + event.setdefault("tags", {}).setdefault( + "executor.id", sc._conf.get("spark.executor.id") + ) + event["tags"].setdefault( + "spark-submit.deployMode", + sc._conf.get("spark.submit.deployMode"), + ) + event["tags"].setdefault("driver.host", sc._conf.get("spark.driver.host")) + event["tags"].setdefault("driver.port", sc._conf.get("spark.driver.port")) + event["tags"].setdefault("spark_version", sc.version) + event["tags"].setdefault("app_name", sc.appName) + event["tags"].setdefault("application_id", sc.applicationId) + event["tags"].setdefault("master", sc.master) + event["tags"].setdefault("spark_home", sc.sparkHome) + + event.setdefault("extra", {}).setdefault("web_url", sc.uiWebUrl) + + return event + + +def _activate_integration(sc): + # type: (SparkContext) -> None + + _start_sentry_listener(sc) + _set_app_properties() + _add_event_processor(sc) + + +def _patch_spark_context_init(): + # type: () -> None + from pyspark import SparkContext + + spark_context_init = SparkContext._do_init + + @ensure_integration_enabled(SparkIntegration, spark_context_init) + def _sentry_patched_spark_context_init(self, *args, **kwargs): + # type: (SparkContext, *Any, **Any) -> Optional[Any] + rv = spark_context_init(self, *args, **kwargs) + _activate_integration(self) + return rv + + SparkContext._do_init = _sentry_patched_spark_context_init + + +def _setup_sentry_tracing(): + # type: () -> None + from pyspark import SparkContext + + if SparkContext._active_spark_context is not None: + _activate_integration(SparkContext._active_spark_context) + return + _patch_spark_context_init() + + +class SparkListener: + def onApplicationEnd(self, applicationEnd): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onApplicationStart(self, applicationStart): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onBlockManagerAdded(self, blockManagerAdded): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onBlockManagerRemoved(self, blockManagerRemoved): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onBlockUpdated(self, blockUpdated): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onEnvironmentUpdate(self, environmentUpdate): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onExecutorAdded(self, executorAdded): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onExecutorBlacklisted(self, executorBlacklisted): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onExecutorBlacklistedForStage( # noqa: N802 + self, executorBlacklistedForStage # noqa: N803 + ): + # type: (Any) -> None + pass + + def onExecutorMetricsUpdate(self, executorMetricsUpdate): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onExecutorRemoved(self, executorRemoved): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onJobEnd(self, jobEnd): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onJobStart(self, jobStart): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onNodeBlacklisted(self, nodeBlacklisted): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onNodeBlacklistedForStage(self, nodeBlacklistedForStage): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onNodeUnblacklisted(self, nodeUnblacklisted): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onOtherEvent(self, event): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onSpeculativeTaskSubmitted(self, speculativeTask): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onStageCompleted(self, stageCompleted): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onStageSubmitted(self, stageSubmitted): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onTaskEnd(self, taskEnd): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onTaskGettingResult(self, taskGettingResult): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onTaskStart(self, taskStart): # noqa: N802,N803 + # type: (Any) -> None + pass + + def onUnpersistRDD(self, unpersistRDD): # noqa: N802,N803 + # type: (Any) -> None + pass + + class Java: + implements = ["org.apache.spark.scheduler.SparkListenerInterface"] + + +class SentryListener(SparkListener): + def _add_breadcrumb( + self, + level, # type: str + message, # type: str + data=None, # type: Optional[dict[str, Any]] + ): + # type: (...) -> None + sentry_sdk.get_global_scope().add_breadcrumb( + level=level, message=message, data=data + ) + + def onJobStart(self, jobStart): # noqa: N802,N803 + # type: (Any) -> None + message = "Job {} Started".format(jobStart.jobId()) + self._add_breadcrumb(level="info", message=message) + _set_app_properties() + + def onJobEnd(self, jobEnd): # noqa: N802,N803 + # type: (Any) -> None + level = "" + message = "" + data = {"result": jobEnd.jobResult().toString()} + + if jobEnd.jobResult().toString() == "JobSucceeded": + level = "info" + message = "Job {} Ended".format(jobEnd.jobId()) + else: + level = "warning" + message = "Job {} Failed".format(jobEnd.jobId()) + + self._add_breadcrumb(level=level, message=message, data=data) + + def onStageSubmitted(self, stageSubmitted): # noqa: N802,N803 + # type: (Any) -> None + stage_info = stageSubmitted.stageInfo() + message = "Stage {} Submitted".format(stage_info.stageId()) + data = {"attemptId": stage_info.attemptId(), "name": stage_info.name()} + self._add_breadcrumb(level="info", message=message, data=data) + _set_app_properties() + + def onStageCompleted(self, stageCompleted): # noqa: N802,N803 + # type: (Any) -> None + from py4j.protocol import Py4JJavaError # type: ignore + + stage_info = stageCompleted.stageInfo() + message = "" + level = "" + data = {"attemptId": stage_info.attemptId(), "name": stage_info.name()} + + # Have to Try Except because stageInfo.failureReason() is typed with Scala Option + try: + data["reason"] = stage_info.failureReason().get() + message = "Stage {} Failed".format(stage_info.stageId()) + level = "warning" + except Py4JJavaError: + message = "Stage {} Completed".format(stage_info.stageId()) + level = "info" + + self._add_breadcrumb(level=level, message=message, data=data) diff --git a/aws/lambda_demo/sentry_sdk/integrations/spark/spark_worker.py b/aws/lambda_demo/sentry_sdk/integrations/spark/spark_worker.py new file mode 100644 index 000000000..5340a0b35 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/spark/spark_worker.py @@ -0,0 +1,116 @@ +import sys + +import sentry_sdk +from sentry_sdk.integrations import Integration +from sentry_sdk.utils import ( + capture_internal_exceptions, + exc_info_from_error, + single_exception_from_error_tuple, + walk_exception_chain, + event_hint_with_exc_info, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Optional + + from sentry_sdk._types import ExcInfo, Event, Hint + + +class SparkWorkerIntegration(Integration): + identifier = "spark_worker" + + @staticmethod + def setup_once(): + # type: () -> None + import pyspark.daemon as original_daemon + + original_daemon.worker_main = _sentry_worker_main + + +def _capture_exception(exc_info): + # type: (ExcInfo) -> None + client = sentry_sdk.get_client() + + mechanism = {"type": "spark", "handled": False} + + exc_info = exc_info_from_error(exc_info) + + exc_type, exc_value, tb = exc_info + rv = [] + + # On Exception worker will call sys.exit(-1), so we can ignore SystemExit and similar errors + for exc_type, exc_value, tb in walk_exception_chain(exc_info): + if exc_type not in (SystemExit, EOFError, ConnectionResetError): + rv.append( + single_exception_from_error_tuple( + exc_type, exc_value, tb, client.options, mechanism + ) + ) + + if rv: + rv.reverse() + hint = event_hint_with_exc_info(exc_info) + event = {"level": "error", "exception": {"values": rv}} # type: Event + + _tag_task_context() + + sentry_sdk.capture_event(event, hint=hint) + + +def _tag_task_context(): + # type: () -> None + from pyspark.taskcontext import TaskContext + + scope = sentry_sdk.get_isolation_scope() + + @scope.add_event_processor + def process_event(event, hint): + # type: (Event, Hint) -> Optional[Event] + with capture_internal_exceptions(): + integration = sentry_sdk.get_client().get_integration( + SparkWorkerIntegration + ) + task_context = TaskContext.get() + + if integration is None or task_context is None: + return event + + event.setdefault("tags", {}).setdefault( + "stageId", str(task_context.stageId()) + ) + event["tags"].setdefault("partitionId", str(task_context.partitionId())) + event["tags"].setdefault("attemptNumber", str(task_context.attemptNumber())) + event["tags"].setdefault("taskAttemptId", str(task_context.taskAttemptId())) + + if task_context._localProperties: + if "sentry_app_name" in task_context._localProperties: + event["tags"].setdefault( + "app_name", task_context._localProperties["sentry_app_name"] + ) + event["tags"].setdefault( + "application_id", + task_context._localProperties["sentry_application_id"], + ) + + if "callSite.short" in task_context._localProperties: + event.setdefault("extra", {}).setdefault( + "callSite", task_context._localProperties["callSite.short"] + ) + + return event + + +def _sentry_worker_main(*args, **kwargs): + # type: (*Optional[Any], **Optional[Any]) -> None + import pyspark.worker as original_worker + + try: + original_worker.main(*args, **kwargs) + except SystemExit: + if sentry_sdk.get_client().get_integration(SparkWorkerIntegration) is not None: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + _capture_exception(exc_info) diff --git a/aws/lambda_demo/sentry_sdk/integrations/sqlalchemy.py b/aws/lambda_demo/sentry_sdk/integrations/sqlalchemy.py new file mode 100644 index 000000000..068d37305 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/sqlalchemy.py @@ -0,0 +1,146 @@ +from sentry_sdk.consts import SPANSTATUS, SPANDATA +from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable +from sentry_sdk.tracing_utils import add_query_source, record_sql_queries +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + parse_version, +) + +try: + from sqlalchemy.engine import Engine # type: ignore + from sqlalchemy.event import listen # type: ignore + from sqlalchemy import __version__ as SQLALCHEMY_VERSION # type: ignore +except ImportError: + raise DidNotEnable("SQLAlchemy not installed.") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import ContextManager + from typing import Optional + + from sentry_sdk.tracing import Span + + +class SqlalchemyIntegration(Integration): + identifier = "sqlalchemy" + origin = f"auto.db.{identifier}" + + @staticmethod + def setup_once(): + # type: () -> None + version = parse_version(SQLALCHEMY_VERSION) + _check_minimum_version(SqlalchemyIntegration, version) + + listen(Engine, "before_cursor_execute", _before_cursor_execute) + listen(Engine, "after_cursor_execute", _after_cursor_execute) + listen(Engine, "handle_error", _handle_error) + + +@ensure_integration_enabled(SqlalchemyIntegration) +def _before_cursor_execute( + conn, cursor, statement, parameters, context, executemany, *args +): + # type: (Any, Any, Any, Any, Any, bool, *Any) -> None + ctx_mgr = record_sql_queries( + cursor, + statement, + parameters, + paramstyle=context and context.dialect and context.dialect.paramstyle or None, + executemany=executemany, + span_origin=SqlalchemyIntegration.origin, + ) + context._sentry_sql_span_manager = ctx_mgr + + span = ctx_mgr.__enter__() + + if span is not None: + _set_db_data(span, conn) + context._sentry_sql_span = span + + +@ensure_integration_enabled(SqlalchemyIntegration) +def _after_cursor_execute(conn, cursor, statement, parameters, context, *args): + # type: (Any, Any, Any, Any, Any, *Any) -> None + ctx_mgr = getattr( + context, "_sentry_sql_span_manager", None + ) # type: Optional[ContextManager[Any]] + + if ctx_mgr is not None: + context._sentry_sql_span_manager = None + ctx_mgr.__exit__(None, None, None) + + span = getattr(context, "_sentry_sql_span", None) # type: Optional[Span] + if span is not None: + with capture_internal_exceptions(): + add_query_source(span) + + +def _handle_error(context, *args): + # type: (Any, *Any) -> None + execution_context = context.execution_context + if execution_context is None: + return + + span = getattr(execution_context, "_sentry_sql_span", None) # type: Optional[Span] + + if span is not None: + span.set_status(SPANSTATUS.INTERNAL_ERROR) + + # _after_cursor_execute does not get called for crashing SQL stmts. Judging + # from SQLAlchemy codebase it does seem like any error coming into this + # handler is going to be fatal. + ctx_mgr = getattr( + execution_context, "_sentry_sql_span_manager", None + ) # type: Optional[ContextManager[Any]] + + if ctx_mgr is not None: + execution_context._sentry_sql_span_manager = None + ctx_mgr.__exit__(None, None, None) + + +# See: https://docs.sqlalchemy.org/en/20/dialects/index.html +def _get_db_system(name): + # type: (str) -> Optional[str] + name = str(name) + + if "sqlite" in name: + return "sqlite" + + if "postgres" in name: + return "postgresql" + + if "mariadb" in name: + return "mariadb" + + if "mysql" in name: + return "mysql" + + if "oracle" in name: + return "oracle" + + return None + + +def _set_db_data(span, conn): + # type: (Span, Any) -> None + db_system = _get_db_system(conn.engine.name) + if db_system is not None: + span.set_data(SPANDATA.DB_SYSTEM, db_system) + + if conn.engine.url is None: + return + + db_name = conn.engine.url.database + if db_name is not None: + span.set_data(SPANDATA.DB_NAME, db_name) + + server_address = conn.engine.url.host + if server_address is not None: + span.set_data(SPANDATA.SERVER_ADDRESS, server_address) + + server_port = conn.engine.url.port + if server_port is not None: + span.set_data(SPANDATA.SERVER_PORT, server_port) diff --git a/aws/lambda_demo/sentry_sdk/integrations/starlette.py b/aws/lambda_demo/sentry_sdk/integrations/starlette.py new file mode 100644 index 000000000..d9db8bd6b --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/starlette.py @@ -0,0 +1,737 @@ +import asyncio +import functools +import warnings +from collections.abc import Set +from copy import deepcopy + +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.integrations import ( + DidNotEnable, + Integration, + _DEFAULT_FAILED_REQUEST_STATUS_CODES, +) +from sentry_sdk.integrations._wsgi_common import ( + DEFAULT_HTTP_METHODS_TO_CAPTURE, + HttpCodeRangeContainer, + _is_json_content_type, + request_body_within_bounds, +) +from sentry_sdk.integrations.asgi import SentryAsgiMiddleware +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import ( + SOURCE_FOR_STYLE, + TRANSACTION_SOURCE_COMPONENT, + TRANSACTION_SOURCE_ROUTE, +) +from sentry_sdk.utils import ( + AnnotatedValue, + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + logger, + parse_version, + transaction_from_function, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Awaitable, Callable, Container, Dict, Optional, Tuple, Union + + from sentry_sdk._types import Event, HttpStatusCodeRange + +try: + import starlette # type: ignore + from starlette import __version__ as STARLETTE_VERSION + from starlette.applications import Starlette # type: ignore + from starlette.datastructures import UploadFile # type: ignore + from starlette.middleware import Middleware # type: ignore + from starlette.middleware.authentication import ( # type: ignore + AuthenticationMiddleware, + ) + from starlette.requests import Request # type: ignore + from starlette.routing import Match # type: ignore + from starlette.types import ASGIApp, Receive, Scope as StarletteScope, Send # type: ignore +except ImportError: + raise DidNotEnable("Starlette is not installed") + +try: + # Starlette 0.20 + from starlette.middleware.exceptions import ExceptionMiddleware # type: ignore +except ImportError: + # Startlette 0.19.1 + from starlette.exceptions import ExceptionMiddleware # type: ignore + +try: + # Optional dependency of Starlette to parse form data. + try: + # python-multipart 0.0.13 and later + import python_multipart as multipart # type: ignore + except ImportError: + # python-multipart 0.0.12 and earlier + import multipart # type: ignore +except ImportError: + multipart = None + + +_DEFAULT_TRANSACTION_NAME = "generic Starlette request" + +TRANSACTION_STYLE_VALUES = ("endpoint", "url") + + +class StarletteIntegration(Integration): + identifier = "starlette" + origin = f"auto.http.{identifier}" + + transaction_style = "" + + def __init__( + self, + transaction_style="url", # type: str + failed_request_status_codes=_DEFAULT_FAILED_REQUEST_STATUS_CODES, # type: Union[Set[int], list[HttpStatusCodeRange], None] + middleware_spans=True, # type: bool + http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: tuple[str, ...] + ): + # type: (...) -> None + if transaction_style not in TRANSACTION_STYLE_VALUES: + raise ValueError( + "Invalid value for transaction_style: %s (must be in %s)" + % (transaction_style, TRANSACTION_STYLE_VALUES) + ) + self.transaction_style = transaction_style + self.middleware_spans = middleware_spans + self.http_methods_to_capture = tuple(map(str.upper, http_methods_to_capture)) + + if isinstance(failed_request_status_codes, Set): + self.failed_request_status_codes = ( + failed_request_status_codes + ) # type: Container[int] + else: + warnings.warn( + "Passing a list or None for failed_request_status_codes is deprecated. " + "Please pass a set of int instead.", + DeprecationWarning, + stacklevel=2, + ) + + if failed_request_status_codes is None: + self.failed_request_status_codes = _DEFAULT_FAILED_REQUEST_STATUS_CODES + else: + self.failed_request_status_codes = HttpCodeRangeContainer( + failed_request_status_codes + ) + + @staticmethod + def setup_once(): + # type: () -> None + version = parse_version(STARLETTE_VERSION) + + if version is None: + raise DidNotEnable( + "Unparsable Starlette version: {}".format(STARLETTE_VERSION) + ) + + patch_middlewares() + patch_asgi_app() + patch_request_response() + + if version >= (0, 24): + patch_templates() + + +def _enable_span_for_middleware(middleware_class): + # type: (Any) -> type + old_call = middleware_class.__call__ + + async def _create_span_call(app, scope, receive, send, **kwargs): + # type: (Any, Dict[str, Any], Callable[[], Awaitable[Dict[str, Any]]], Callable[[Dict[str, Any]], Awaitable[None]], Any) -> None + integration = sentry_sdk.get_client().get_integration(StarletteIntegration) + if integration is None or not integration.middleware_spans: + return await old_call(app, scope, receive, send, **kwargs) + + middleware_name = app.__class__.__name__ + + # Update transaction name with middleware name + name, source = _get_transaction_from_middleware(app, scope, integration) + if name is not None: + sentry_sdk.get_current_scope().set_transaction_name( + name, + source=source, + ) + + with sentry_sdk.start_span( + op=OP.MIDDLEWARE_STARLETTE, + name=middleware_name, + origin=StarletteIntegration.origin, + ) as middleware_span: + middleware_span.set_tag("starlette.middleware_name", middleware_name) + + # Creating spans for the "receive" callback + async def _sentry_receive(*args, **kwargs): + # type: (*Any, **Any) -> Any + with sentry_sdk.start_span( + op=OP.MIDDLEWARE_STARLETTE_RECEIVE, + name=getattr(receive, "__qualname__", str(receive)), + origin=StarletteIntegration.origin, + ) as span: + span.set_tag("starlette.middleware_name", middleware_name) + return await receive(*args, **kwargs) + + receive_name = getattr(receive, "__name__", str(receive)) + receive_patched = receive_name == "_sentry_receive" + new_receive = _sentry_receive if not receive_patched else receive + + # Creating spans for the "send" callback + async def _sentry_send(*args, **kwargs): + # type: (*Any, **Any) -> Any + with sentry_sdk.start_span( + op=OP.MIDDLEWARE_STARLETTE_SEND, + name=getattr(send, "__qualname__", str(send)), + origin=StarletteIntegration.origin, + ) as span: + span.set_tag("starlette.middleware_name", middleware_name) + return await send(*args, **kwargs) + + send_name = getattr(send, "__name__", str(send)) + send_patched = send_name == "_sentry_send" + new_send = _sentry_send if not send_patched else send + + return await old_call(app, scope, new_receive, new_send, **kwargs) + + not_yet_patched = old_call.__name__ not in [ + "_create_span_call", + "_sentry_authenticationmiddleware_call", + "_sentry_exceptionmiddleware_call", + ] + + if not_yet_patched: + middleware_class.__call__ = _create_span_call + + return middleware_class + + +@ensure_integration_enabled(StarletteIntegration) +def _capture_exception(exception, handled=False): + # type: (BaseException, **Any) -> None + event, hint = event_from_exception( + exception, + client_options=sentry_sdk.get_client().options, + mechanism={"type": StarletteIntegration.identifier, "handled": handled}, + ) + + sentry_sdk.capture_event(event, hint=hint) + + +def patch_exception_middleware(middleware_class): + # type: (Any) -> None + """ + Capture all exceptions in Starlette app and + also extract user information. + """ + old_middleware_init = middleware_class.__init__ + + not_yet_patched = "_sentry_middleware_init" not in str(old_middleware_init) + + if not_yet_patched: + + def _sentry_middleware_init(self, *args, **kwargs): + # type: (Any, Any, Any) -> None + old_middleware_init(self, *args, **kwargs) + + # Patch existing exception handlers + old_handlers = self._exception_handlers.copy() + + async def _sentry_patched_exception_handler(self, *args, **kwargs): + # type: (Any, Any, Any) -> None + integration = sentry_sdk.get_client().get_integration( + StarletteIntegration + ) + + exp = args[0] + + if integration is not None: + is_http_server_error = ( + hasattr(exp, "status_code") + and isinstance(exp.status_code, int) + and exp.status_code in integration.failed_request_status_codes + ) + if is_http_server_error: + _capture_exception(exp, handled=True) + + # Find a matching handler + old_handler = None + for cls in type(exp).__mro__: + if cls in old_handlers: + old_handler = old_handlers[cls] + break + + if old_handler is None: + return + + if _is_async_callable(old_handler): + return await old_handler(self, *args, **kwargs) + else: + return old_handler(self, *args, **kwargs) + + for key in self._exception_handlers.keys(): + self._exception_handlers[key] = _sentry_patched_exception_handler + + middleware_class.__init__ = _sentry_middleware_init + + old_call = middleware_class.__call__ + + async def _sentry_exceptionmiddleware_call(self, scope, receive, send): + # type: (Dict[str, Any], Dict[str, Any], Callable[[], Awaitable[Dict[str, Any]]], Callable[[Dict[str, Any]], Awaitable[None]]) -> None + # Also add the user (that was eventually set by be Authentication middle + # that was called before this middleware). This is done because the authentication + # middleware sets the user in the scope and then (in the same function) + # calls this exception middelware. In case there is no exception (or no handler + # for the type of exception occuring) then the exception bubbles up and setting the + # user information into the sentry scope is done in auth middleware and the + # ASGI middleware will then send everything to Sentry and this is fine. + # But if there is an exception happening that the exception middleware here + # has a handler for, it will send the exception directly to Sentry, so we need + # the user information right now. + # This is why we do it here. + _add_user_to_sentry_scope(scope) + await old_call(self, scope, receive, send) + + middleware_class.__call__ = _sentry_exceptionmiddleware_call + + +@ensure_integration_enabled(StarletteIntegration) +def _add_user_to_sentry_scope(scope): + # type: (Dict[str, Any]) -> None + """ + Extracts user information from the ASGI scope and + adds it to Sentry's scope. + """ + if "user" not in scope: + return + + if not should_send_default_pii(): + return + + user_info = {} # type: Dict[str, Any] + starlette_user = scope["user"] + + username = getattr(starlette_user, "username", None) + if username: + user_info.setdefault("username", starlette_user.username) + + user_id = getattr(starlette_user, "id", None) + if user_id: + user_info.setdefault("id", starlette_user.id) + + email = getattr(starlette_user, "email", None) + if email: + user_info.setdefault("email", starlette_user.email) + + sentry_scope = sentry_sdk.get_isolation_scope() + sentry_scope.user = user_info + + +def patch_authentication_middleware(middleware_class): + # type: (Any) -> None + """ + Add user information to Sentry scope. + """ + old_call = middleware_class.__call__ + + not_yet_patched = "_sentry_authenticationmiddleware_call" not in str(old_call) + + if not_yet_patched: + + async def _sentry_authenticationmiddleware_call(self, scope, receive, send): + # type: (Dict[str, Any], Dict[str, Any], Callable[[], Awaitable[Dict[str, Any]]], Callable[[Dict[str, Any]], Awaitable[None]]) -> None + await old_call(self, scope, receive, send) + _add_user_to_sentry_scope(scope) + + middleware_class.__call__ = _sentry_authenticationmiddleware_call + + +def patch_middlewares(): + # type: () -> None + """ + Patches Starlettes `Middleware` class to record + spans for every middleware invoked. + """ + old_middleware_init = Middleware.__init__ + + not_yet_patched = "_sentry_middleware_init" not in str(old_middleware_init) + + if not_yet_patched: + + def _sentry_middleware_init(self, cls, **options): + # type: (Any, Any, Any) -> None + if cls == SentryAsgiMiddleware: + return old_middleware_init(self, cls, **options) + + span_enabled_cls = _enable_span_for_middleware(cls) + old_middleware_init(self, span_enabled_cls, **options) + + if cls == AuthenticationMiddleware: + patch_authentication_middleware(cls) + + if cls == ExceptionMiddleware: + patch_exception_middleware(cls) + + Middleware.__init__ = _sentry_middleware_init + + +def patch_asgi_app(): + # type: () -> None + """ + Instrument Starlette ASGI app using the SentryAsgiMiddleware. + """ + old_app = Starlette.__call__ + + async def _sentry_patched_asgi_app(self, scope, receive, send): + # type: (Starlette, StarletteScope, Receive, Send) -> None + integration = sentry_sdk.get_client().get_integration(StarletteIntegration) + if integration is None: + return await old_app(self, scope, receive, send) + + middleware = SentryAsgiMiddleware( + lambda *a, **kw: old_app(self, *a, **kw), + mechanism_type=StarletteIntegration.identifier, + transaction_style=integration.transaction_style, + span_origin=StarletteIntegration.origin, + http_methods_to_capture=( + integration.http_methods_to_capture + if integration + else DEFAULT_HTTP_METHODS_TO_CAPTURE + ), + ) + + middleware.__call__ = middleware._run_asgi3 + return await middleware(scope, receive, send) + + Starlette.__call__ = _sentry_patched_asgi_app + + +# This was vendored in from Starlette to support Starlette 0.19.1 because +# this function was only introduced in 0.20.x +def _is_async_callable(obj): + # type: (Any) -> bool + while isinstance(obj, functools.partial): + obj = obj.func + + return asyncio.iscoroutinefunction(obj) or ( + callable(obj) and asyncio.iscoroutinefunction(obj.__call__) + ) + + +def patch_request_response(): + # type: () -> None + old_request_response = starlette.routing.request_response + + def _sentry_request_response(func): + # type: (Callable[[Any], Any]) -> ASGIApp + old_func = func + + is_coroutine = _is_async_callable(old_func) + if is_coroutine: + + async def _sentry_async_func(*args, **kwargs): + # type: (*Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration( + StarletteIntegration + ) + if integration is None: + return await old_func(*args, **kwargs) + + request = args[0] + + _set_transaction_name_and_source( + sentry_sdk.get_current_scope(), + integration.transaction_style, + request, + ) + + sentry_scope = sentry_sdk.get_isolation_scope() + extractor = StarletteRequestExtractor(request) + info = await extractor.extract_request_info() + + def _make_request_event_processor(req, integration): + # type: (Any, Any) -> Callable[[Event, dict[str, Any]], Event] + def event_processor(event, hint): + # type: (Event, Dict[str, Any]) -> Event + + # Add info from request to event + request_info = event.get("request", {}) + if info: + if "cookies" in info: + request_info["cookies"] = info["cookies"] + if "data" in info: + request_info["data"] = info["data"] + event["request"] = deepcopy(request_info) + + return event + + return event_processor + + sentry_scope._name = StarletteIntegration.identifier + sentry_scope.add_event_processor( + _make_request_event_processor(request, integration) + ) + + return await old_func(*args, **kwargs) + + func = _sentry_async_func + + else: + + @functools.wraps(old_func) + def _sentry_sync_func(*args, **kwargs): + # type: (*Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration( + StarletteIntegration + ) + if integration is None: + return old_func(*args, **kwargs) + + current_scope = sentry_sdk.get_current_scope() + if current_scope.transaction is not None: + current_scope.transaction.update_active_thread() + + sentry_scope = sentry_sdk.get_isolation_scope() + if sentry_scope.profile is not None: + sentry_scope.profile.update_active_thread_id() + + request = args[0] + + _set_transaction_name_and_source( + sentry_scope, integration.transaction_style, request + ) + + extractor = StarletteRequestExtractor(request) + cookies = extractor.extract_cookies_from_request() + + def _make_request_event_processor(req, integration): + # type: (Any, Any) -> Callable[[Event, dict[str, Any]], Event] + def event_processor(event, hint): + # type: (Event, dict[str, Any]) -> Event + + # Extract information from request + request_info = event.get("request", {}) + if cookies: + request_info["cookies"] = cookies + + event["request"] = deepcopy(request_info) + + return event + + return event_processor + + sentry_scope._name = StarletteIntegration.identifier + sentry_scope.add_event_processor( + _make_request_event_processor(request, integration) + ) + + return old_func(*args, **kwargs) + + func = _sentry_sync_func + + return old_request_response(func) + + starlette.routing.request_response = _sentry_request_response + + +def patch_templates(): + # type: () -> None + + # If markupsafe is not installed, then Jinja2 is not installed + # (markupsafe is a dependency of Jinja2) + # In this case we do not need to patch the Jinja2Templates class + try: + from markupsafe import Markup + except ImportError: + return # Nothing to do + + from starlette.templating import Jinja2Templates # type: ignore + + old_jinja2templates_init = Jinja2Templates.__init__ + + not_yet_patched = "_sentry_jinja2templates_init" not in str( + old_jinja2templates_init + ) + + if not_yet_patched: + + def _sentry_jinja2templates_init(self, *args, **kwargs): + # type: (Jinja2Templates, *Any, **Any) -> None + def add_sentry_trace_meta(request): + # type: (Request) -> Dict[str, Any] + trace_meta = Markup( + sentry_sdk.get_current_scope().trace_propagation_meta() + ) + return { + "sentry_trace_meta": trace_meta, + } + + kwargs.setdefault("context_processors", []) + + if add_sentry_trace_meta not in kwargs["context_processors"]: + kwargs["context_processors"].append(add_sentry_trace_meta) + + return old_jinja2templates_init(self, *args, **kwargs) + + Jinja2Templates.__init__ = _sentry_jinja2templates_init + + +class StarletteRequestExtractor: + """ + Extracts useful information from the Starlette request + (like form data or cookies) and adds it to the Sentry event. + """ + + request = None # type: Request + + def __init__(self, request): + # type: (StarletteRequestExtractor, Request) -> None + self.request = request + + def extract_cookies_from_request(self): + # type: (StarletteRequestExtractor) -> Optional[Dict[str, Any]] + cookies = None # type: Optional[Dict[str, Any]] + if should_send_default_pii(): + cookies = self.cookies() + + return cookies + + async def extract_request_info(self): + # type: (StarletteRequestExtractor) -> Optional[Dict[str, Any]] + client = sentry_sdk.get_client() + + request_info = {} # type: Dict[str, Any] + + with capture_internal_exceptions(): + # Add cookies + if should_send_default_pii(): + request_info["cookies"] = self.cookies() + + # If there is no body, just return the cookies + content_length = await self.content_length() + if not content_length: + return request_info + + # Add annotation if body is too big + if content_length and not request_body_within_bounds( + client, content_length + ): + request_info["data"] = AnnotatedValue.removed_because_over_size_limit() + return request_info + + # Add JSON body, if it is a JSON request + json = await self.json() + if json: + request_info["data"] = json + return request_info + + # Add form as key/value pairs, if request has form data + form = await self.form() + if form: + form_data = {} + for key, val in form.items(): + is_file = isinstance(val, UploadFile) + form_data[key] = ( + val + if not is_file + else AnnotatedValue.removed_because_raw_data() + ) + + request_info["data"] = form_data + return request_info + + # Raw data, do not add body just an annotation + request_info["data"] = AnnotatedValue.removed_because_raw_data() + return request_info + + async def content_length(self): + # type: (StarletteRequestExtractor) -> Optional[int] + if "content-length" in self.request.headers: + return int(self.request.headers["content-length"]) + + return None + + def cookies(self): + # type: (StarletteRequestExtractor) -> Dict[str, Any] + return self.request.cookies + + async def form(self): + # type: (StarletteRequestExtractor) -> Any + if multipart is None: + return None + + # Parse the body first to get it cached, as Starlette does not cache form() as it + # does with body() and json() https://github.com/encode/starlette/discussions/1933 + # Calling `.form()` without calling `.body()` first will + # potentially break the users project. + await self.request.body() + + return await self.request.form() + + def is_json(self): + # type: (StarletteRequestExtractor) -> bool + return _is_json_content_type(self.request.headers.get("content-type")) + + async def json(self): + # type: (StarletteRequestExtractor) -> Optional[Dict[str, Any]] + if not self.is_json(): + return None + + return await self.request.json() + + +def _transaction_name_from_router(scope): + # type: (StarletteScope) -> Optional[str] + router = scope.get("router") + if not router: + return None + + for route in router.routes: + match = route.matches(scope) + if match[0] == Match.FULL: + return route.path + + return None + + +def _set_transaction_name_and_source(scope, transaction_style, request): + # type: (sentry_sdk.Scope, str, Any) -> None + name = None + source = SOURCE_FOR_STYLE[transaction_style] + + if transaction_style == "endpoint": + endpoint = request.scope.get("endpoint") + if endpoint: + name = transaction_from_function(endpoint) or None + + elif transaction_style == "url": + name = _transaction_name_from_router(request.scope) + + if name is None: + name = _DEFAULT_TRANSACTION_NAME + source = TRANSACTION_SOURCE_ROUTE + + scope.set_transaction_name(name, source=source) + logger.debug( + "[Starlette] Set transaction name and source on scope: %s / %s", name, source + ) + + +def _get_transaction_from_middleware(app, asgi_scope, integration): + # type: (Any, Dict[str, Any], StarletteIntegration) -> Tuple[Optional[str], Optional[str]] + name = None + source = None + + if integration.transaction_style == "endpoint": + name = transaction_from_function(app.__class__) + source = TRANSACTION_SOURCE_COMPONENT + elif integration.transaction_style == "url": + name = _transaction_name_from_router(asgi_scope) + source = TRANSACTION_SOURCE_ROUTE + + return name, source diff --git a/aws/lambda_demo/sentry_sdk/integrations/starlite.py b/aws/lambda_demo/sentry_sdk/integrations/starlite.py new file mode 100644 index 000000000..8714ee2f0 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/starlite.py @@ -0,0 +1,292 @@ +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.integrations.asgi import SentryAsgiMiddleware +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import SOURCE_FOR_STYLE, TRANSACTION_SOURCE_ROUTE +from sentry_sdk.utils import ( + ensure_integration_enabled, + event_from_exception, + transaction_from_function, +) + +try: + from starlite import Request, Starlite, State # type: ignore + from starlite.handlers.base import BaseRouteHandler # type: ignore + from starlite.middleware import DefineMiddleware # type: ignore + from starlite.plugins.base import get_plugin_for_value # type: ignore + from starlite.routes.http import HTTPRoute # type: ignore + from starlite.utils import ConnectionDataExtractor, is_async_callable, Ref # type: ignore + from pydantic import BaseModel # type: ignore +except ImportError: + raise DidNotEnable("Starlite is not installed") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Optional, Union + from starlite.types import ( # type: ignore + ASGIApp, + Hint, + HTTPReceiveMessage, + HTTPScope, + Message, + Middleware, + Receive, + Scope as StarliteScope, + Send, + WebSocketReceiveMessage, + ) + from starlite import MiddlewareProtocol + from sentry_sdk._types import Event + + +_DEFAULT_TRANSACTION_NAME = "generic Starlite request" + + +class StarliteIntegration(Integration): + identifier = "starlite" + origin = f"auto.http.{identifier}" + + @staticmethod + def setup_once(): + # type: () -> None + patch_app_init() + patch_middlewares() + patch_http_route_handle() + + +class SentryStarliteASGIMiddleware(SentryAsgiMiddleware): + def __init__(self, app, span_origin=StarliteIntegration.origin): + # type: (ASGIApp, str) -> None + super().__init__( + app=app, + unsafe_context_data=False, + transaction_style="endpoint", + mechanism_type="asgi", + span_origin=span_origin, + ) + + +def patch_app_init(): + # type: () -> None + """ + Replaces the Starlite class's `__init__` function in order to inject `after_exception` handlers and set the + `SentryStarliteASGIMiddleware` as the outmost middleware in the stack. + See: + - https://starlite-api.github.io/starlite/usage/0-the-starlite-app/5-application-hooks/#after-exception + - https://starlite-api.github.io/starlite/usage/7-middleware/0-middleware-intro/ + """ + old__init__ = Starlite.__init__ + + @ensure_integration_enabled(StarliteIntegration, old__init__) + def injection_wrapper(self, *args, **kwargs): + # type: (Starlite, *Any, **Any) -> None + after_exception = kwargs.pop("after_exception", []) + kwargs.update( + after_exception=[ + exception_handler, + *( + after_exception + if isinstance(after_exception, list) + else [after_exception] + ), + ] + ) + + SentryStarliteASGIMiddleware.__call__ = SentryStarliteASGIMiddleware._run_asgi3 # type: ignore + middleware = kwargs.get("middleware") or [] + kwargs["middleware"] = [SentryStarliteASGIMiddleware, *middleware] + old__init__(self, *args, **kwargs) + + Starlite.__init__ = injection_wrapper + + +def patch_middlewares(): + # type: () -> None + old_resolve_middleware_stack = BaseRouteHandler.resolve_middleware + + @ensure_integration_enabled(StarliteIntegration, old_resolve_middleware_stack) + def resolve_middleware_wrapper(self): + # type: (BaseRouteHandler) -> list[Middleware] + return [ + enable_span_for_middleware(middleware) + for middleware in old_resolve_middleware_stack(self) + ] + + BaseRouteHandler.resolve_middleware = resolve_middleware_wrapper + + +def enable_span_for_middleware(middleware): + # type: (Middleware) -> Middleware + if ( + not hasattr(middleware, "__call__") # noqa: B004 + or middleware is SentryStarliteASGIMiddleware + ): + return middleware + + if isinstance(middleware, DefineMiddleware): + old_call = middleware.middleware.__call__ # type: ASGIApp + else: + old_call = middleware.__call__ + + async def _create_span_call(self, scope, receive, send): + # type: (MiddlewareProtocol, StarliteScope, Receive, Send) -> None + if sentry_sdk.get_client().get_integration(StarliteIntegration) is None: + return await old_call(self, scope, receive, send) + + middleware_name = self.__class__.__name__ + with sentry_sdk.start_span( + op=OP.MIDDLEWARE_STARLITE, + name=middleware_name, + origin=StarliteIntegration.origin, + ) as middleware_span: + middleware_span.set_tag("starlite.middleware_name", middleware_name) + + # Creating spans for the "receive" callback + async def _sentry_receive(*args, **kwargs): + # type: (*Any, **Any) -> Union[HTTPReceiveMessage, WebSocketReceiveMessage] + if sentry_sdk.get_client().get_integration(StarliteIntegration) is None: + return await receive(*args, **kwargs) + with sentry_sdk.start_span( + op=OP.MIDDLEWARE_STARLITE_RECEIVE, + name=getattr(receive, "__qualname__", str(receive)), + origin=StarliteIntegration.origin, + ) as span: + span.set_tag("starlite.middleware_name", middleware_name) + return await receive(*args, **kwargs) + + receive_name = getattr(receive, "__name__", str(receive)) + receive_patched = receive_name == "_sentry_receive" + new_receive = _sentry_receive if not receive_patched else receive + + # Creating spans for the "send" callback + async def _sentry_send(message): + # type: (Message) -> None + if sentry_sdk.get_client().get_integration(StarliteIntegration) is None: + return await send(message) + with sentry_sdk.start_span( + op=OP.MIDDLEWARE_STARLITE_SEND, + name=getattr(send, "__qualname__", str(send)), + origin=StarliteIntegration.origin, + ) as span: + span.set_tag("starlite.middleware_name", middleware_name) + return await send(message) + + send_name = getattr(send, "__name__", str(send)) + send_patched = send_name == "_sentry_send" + new_send = _sentry_send if not send_patched else send + + return await old_call(self, scope, new_receive, new_send) + + not_yet_patched = old_call.__name__ not in ["_create_span_call"] + + if not_yet_patched: + if isinstance(middleware, DefineMiddleware): + middleware.middleware.__call__ = _create_span_call + else: + middleware.__call__ = _create_span_call + + return middleware + + +def patch_http_route_handle(): + # type: () -> None + old_handle = HTTPRoute.handle + + async def handle_wrapper(self, scope, receive, send): + # type: (HTTPRoute, HTTPScope, Receive, Send) -> None + if sentry_sdk.get_client().get_integration(StarliteIntegration) is None: + return await old_handle(self, scope, receive, send) + + sentry_scope = sentry_sdk.get_isolation_scope() + request = scope["app"].request_class( + scope=scope, receive=receive, send=send + ) # type: Request[Any, Any] + extracted_request_data = ConnectionDataExtractor( + parse_body=True, parse_query=True + )(request) + body = extracted_request_data.pop("body") + + request_data = await body + + def event_processor(event, _): + # type: (Event, Hint) -> Event + route_handler = scope.get("route_handler") + + request_info = event.get("request", {}) + request_info["content_length"] = len(scope.get("_body", b"")) + if should_send_default_pii(): + request_info["cookies"] = extracted_request_data["cookies"] + if request_data is not None: + request_info["data"] = request_data + + func = None + if route_handler.name is not None: + tx_name = route_handler.name + elif isinstance(route_handler.fn, Ref): + func = route_handler.fn.value + else: + func = route_handler.fn + if func is not None: + tx_name = transaction_from_function(func) + + tx_info = {"source": SOURCE_FOR_STYLE["endpoint"]} + + if not tx_name: + tx_name = _DEFAULT_TRANSACTION_NAME + tx_info = {"source": TRANSACTION_SOURCE_ROUTE} + + event.update( + { + "request": request_info, + "transaction": tx_name, + "transaction_info": tx_info, + } + ) + return event + + sentry_scope._name = StarliteIntegration.identifier + sentry_scope.add_event_processor(event_processor) + + return await old_handle(self, scope, receive, send) + + HTTPRoute.handle = handle_wrapper + + +def retrieve_user_from_scope(scope): + # type: (StarliteScope) -> Optional[dict[str, Any]] + scope_user = scope.get("user") + if not scope_user: + return None + if isinstance(scope_user, dict): + return scope_user + if isinstance(scope_user, BaseModel): + return scope_user.dict() + if hasattr(scope_user, "asdict"): # dataclasses + return scope_user.asdict() + + plugin = get_plugin_for_value(scope_user) + if plugin and not is_async_callable(plugin.to_dict): + return plugin.to_dict(scope_user) + + return None + + +@ensure_integration_enabled(StarliteIntegration) +def exception_handler(exc, scope, _): + # type: (Exception, StarliteScope, State) -> None + user_info = None # type: Optional[dict[str, Any]] + if should_send_default_pii(): + user_info = retrieve_user_from_scope(scope) + if user_info and isinstance(user_info, dict): + sentry_scope = sentry_sdk.get_isolation_scope() + sentry_scope.set_user(user_info) + + event, hint = event_from_exception( + exc, + client_options=sentry_sdk.get_client().options, + mechanism={"type": StarliteIntegration.identifier, "handled": False}, + ) + + sentry_sdk.capture_event(event, hint=hint) diff --git a/aws/lambda_demo/sentry_sdk/integrations/stdlib.py b/aws/lambda_demo/sentry_sdk/integrations/stdlib.py new file mode 100644 index 000000000..d388c5bca --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/stdlib.py @@ -0,0 +1,265 @@ +import os +import subprocess +import sys +import platform +from http.client import HTTPConnection + +import sentry_sdk +from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.integrations import Integration +from sentry_sdk.scope import add_global_event_processor +from sentry_sdk.tracing_utils import EnvironHeaders, should_propagate_trace +from sentry_sdk.utils import ( + SENSITIVE_DATA_SUBSTITUTE, + capture_internal_exceptions, + ensure_integration_enabled, + is_sentry_url, + logger, + safe_repr, + parse_url, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import Dict + from typing import Optional + from typing import List + + from sentry_sdk._types import Event, Hint + + +_RUNTIME_CONTEXT = { + "name": platform.python_implementation(), + "version": "%s.%s.%s" % (sys.version_info[:3]), + "build": sys.version, +} # type: dict[str, object] + + +class StdlibIntegration(Integration): + identifier = "stdlib" + + @staticmethod + def setup_once(): + # type: () -> None + _install_httplib() + _install_subprocess() + + @add_global_event_processor + def add_python_runtime_context(event, hint): + # type: (Event, Hint) -> Optional[Event] + if sentry_sdk.get_client().get_integration(StdlibIntegration) is not None: + contexts = event.setdefault("contexts", {}) + if isinstance(contexts, dict) and "runtime" not in contexts: + contexts["runtime"] = _RUNTIME_CONTEXT + + return event + + +def _install_httplib(): + # type: () -> None + real_putrequest = HTTPConnection.putrequest + real_getresponse = HTTPConnection.getresponse + + def putrequest(self, method, url, *args, **kwargs): + # type: (HTTPConnection, str, str, *Any, **Any) -> Any + host = self.host + port = self.port + default_port = self.default_port + + client = sentry_sdk.get_client() + if client.get_integration(StdlibIntegration) is None or is_sentry_url( + client, host + ): + return real_putrequest(self, method, url, *args, **kwargs) + + real_url = url + if real_url is None or not real_url.startswith(("http://", "https://")): + real_url = "%s://%s%s%s" % ( + default_port == 443 and "https" or "http", + host, + port != default_port and ":%s" % port or "", + url, + ) + + parsed_url = None + with capture_internal_exceptions(): + parsed_url = parse_url(real_url, sanitize=False) + + span = sentry_sdk.start_span( + op=OP.HTTP_CLIENT, + name="%s %s" + % (method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE), + origin="auto.http.stdlib.httplib", + ) + span.set_data(SPANDATA.HTTP_METHOD, method) + if parsed_url is not None: + span.set_data("url", parsed_url.url) + span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query) + span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment) + + rv = real_putrequest(self, method, url, *args, **kwargs) + + if should_propagate_trace(client, real_url): + for ( + key, + value, + ) in sentry_sdk.get_current_scope().iter_trace_propagation_headers( + span=span + ): + logger.debug( + "[Tracing] Adding `{key}` header {value} to outgoing request to {real_url}.".format( + key=key, value=value, real_url=real_url + ) + ) + self.putheader(key, value) + + self._sentrysdk_span = span # type: ignore[attr-defined] + + return rv + + def getresponse(self, *args, **kwargs): + # type: (HTTPConnection, *Any, **Any) -> Any + span = getattr(self, "_sentrysdk_span", None) + + if span is None: + return real_getresponse(self, *args, **kwargs) + + try: + rv = real_getresponse(self, *args, **kwargs) + + span.set_http_status(int(rv.status)) + span.set_data("reason", rv.reason) + finally: + span.finish() + + return rv + + HTTPConnection.putrequest = putrequest # type: ignore[method-assign] + HTTPConnection.getresponse = getresponse # type: ignore[method-assign] + + +def _init_argument(args, kwargs, name, position, setdefault_callback=None): + # type: (List[Any], Dict[Any, Any], str, int, Optional[Callable[[Any], Any]]) -> Any + """ + given (*args, **kwargs) of a function call, retrieve (and optionally set a + default for) an argument by either name or position. + + This is useful for wrapping functions with complex type signatures and + extracting a few arguments without needing to redefine that function's + entire type signature. + """ + + if name in kwargs: + rv = kwargs[name] + if setdefault_callback is not None: + rv = setdefault_callback(rv) + if rv is not None: + kwargs[name] = rv + elif position < len(args): + rv = args[position] + if setdefault_callback is not None: + rv = setdefault_callback(rv) + if rv is not None: + args[position] = rv + else: + rv = setdefault_callback and setdefault_callback(None) + if rv is not None: + kwargs[name] = rv + + return rv + + +def _install_subprocess(): + # type: () -> None + old_popen_init = subprocess.Popen.__init__ + + @ensure_integration_enabled(StdlibIntegration, old_popen_init) + def sentry_patched_popen_init(self, *a, **kw): + # type: (subprocess.Popen[Any], *Any, **Any) -> None + # Convert from tuple to list to be able to set values. + a = list(a) + + args = _init_argument(a, kw, "args", 0) or [] + cwd = _init_argument(a, kw, "cwd", 9) + + # if args is not a list or tuple (and e.g. some iterator instead), + # let's not use it at all. There are too many things that can go wrong + # when trying to collect an iterator into a list and setting that list + # into `a` again. + # + # Also invocations where `args` is not a sequence are not actually + # legal. They just happen to work under CPython. + description = None + + if isinstance(args, (list, tuple)) and len(args) < 100: + with capture_internal_exceptions(): + description = " ".join(map(str, args)) + + if description is None: + description = safe_repr(args) + + env = None + + with sentry_sdk.start_span( + op=OP.SUBPROCESS, + name=description, + origin="auto.subprocess.stdlib.subprocess", + ) as span: + for k, v in sentry_sdk.get_current_scope().iter_trace_propagation_headers( + span=span + ): + if env is None: + env = _init_argument( + a, + kw, + "env", + 10, + lambda x: dict(x if x is not None else os.environ), + ) + env["SUBPROCESS_" + k.upper().replace("-", "_")] = v + + if cwd: + span.set_data("subprocess.cwd", cwd) + + rv = old_popen_init(self, *a, **kw) + + span.set_tag("subprocess.pid", self.pid) + return rv + + subprocess.Popen.__init__ = sentry_patched_popen_init # type: ignore + + old_popen_wait = subprocess.Popen.wait + + @ensure_integration_enabled(StdlibIntegration, old_popen_wait) + def sentry_patched_popen_wait(self, *a, **kw): + # type: (subprocess.Popen[Any], *Any, **Any) -> Any + with sentry_sdk.start_span( + op=OP.SUBPROCESS_WAIT, + origin="auto.subprocess.stdlib.subprocess", + ) as span: + span.set_tag("subprocess.pid", self.pid) + return old_popen_wait(self, *a, **kw) + + subprocess.Popen.wait = sentry_patched_popen_wait # type: ignore + + old_popen_communicate = subprocess.Popen.communicate + + @ensure_integration_enabled(StdlibIntegration, old_popen_communicate) + def sentry_patched_popen_communicate(self, *a, **kw): + # type: (subprocess.Popen[Any], *Any, **Any) -> Any + with sentry_sdk.start_span( + op=OP.SUBPROCESS_COMMUNICATE, + origin="auto.subprocess.stdlib.subprocess", + ) as span: + span.set_tag("subprocess.pid", self.pid) + return old_popen_communicate(self, *a, **kw) + + subprocess.Popen.communicate = sentry_patched_popen_communicate # type: ignore + + +def get_subprocess_traceparent_headers(): + # type: () -> EnvironHeaders + return EnvironHeaders(os.environ, prefix="SUBPROCESS_") diff --git a/aws/lambda_demo/sentry_sdk/integrations/strawberry.py b/aws/lambda_demo/sentry_sdk/integrations/strawberry.py new file mode 100644 index 000000000..d27e0eaf1 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/strawberry.py @@ -0,0 +1,425 @@ +import functools +import hashlib +from inspect import isawaitable + +import sentry_sdk +from sentry_sdk.consts import OP +from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable +from sentry_sdk.integrations.logging import ignore_logger +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT +from sentry_sdk.utils import ( + capture_internal_exceptions, + ensure_integration_enabled, + event_from_exception, + logger, + package_version, + _get_installed_modules, +) + +try: + from functools import cached_property +except ImportError: + # The strawberry integration requires Python 3.8+. functools.cached_property + # was added in 3.8, so this check is technically not needed, but since this + # is an auto-enabling integration, we might get to executing this import in + # lower Python versions, so we need to deal with it. + raise DidNotEnable("strawberry-graphql integration requires Python 3.8 or newer") + +try: + import strawberry.schema.schema as strawberry_schema # type: ignore + from strawberry import Schema + from strawberry.extensions import SchemaExtension # type: ignore + from strawberry.extensions.tracing.utils import should_skip_tracing as strawberry_should_skip_tracing # type: ignore + from strawberry.http import async_base_view, sync_base_view # type: ignore +except ImportError: + raise DidNotEnable("strawberry-graphql is not installed") + +try: + from strawberry.extensions.tracing import ( # type: ignore + SentryTracingExtension as StrawberrySentryAsyncExtension, + SentryTracingExtensionSync as StrawberrySentrySyncExtension, + ) +except ImportError: + StrawberrySentryAsyncExtension = None + StrawberrySentrySyncExtension = None + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Callable, Generator, List, Optional, Union + from graphql import GraphQLError, GraphQLResolveInfo # type: ignore + from strawberry.http import GraphQLHTTPResponse + from strawberry.types import ExecutionContext, ExecutionResult, SubscriptionExecutionResult # type: ignore + from sentry_sdk._types import Event, EventProcessor + + +ignore_logger("strawberry.execution") + + +class StrawberryIntegration(Integration): + identifier = "strawberry" + origin = f"auto.graphql.{identifier}" + + def __init__(self, async_execution=None): + # type: (Optional[bool]) -> None + if async_execution not in (None, False, True): + raise ValueError( + 'Invalid value for async_execution: "{}" (must be bool)'.format( + async_execution + ) + ) + self.async_execution = async_execution + + @staticmethod + def setup_once(): + # type: () -> None + version = package_version("strawberry-graphql") + _check_minimum_version(StrawberryIntegration, version, "strawberry-graphql") + + _patch_schema_init() + _patch_execute() + _patch_views() + + +def _patch_schema_init(): + # type: () -> None + old_schema_init = Schema.__init__ + + @functools.wraps(old_schema_init) + def _sentry_patched_schema_init(self, *args, **kwargs): + # type: (Schema, Any, Any) -> None + integration = sentry_sdk.get_client().get_integration(StrawberryIntegration) + if integration is None: + return old_schema_init(self, *args, **kwargs) + + extensions = kwargs.get("extensions") or [] + + if integration.async_execution is not None: + should_use_async_extension = integration.async_execution + else: + # try to figure it out ourselves + should_use_async_extension = _guess_if_using_async(extensions) + + logger.info( + "Assuming strawberry is running %s. If not, initialize it as StrawberryIntegration(async_execution=%s).", + "async" if should_use_async_extension else "sync", + "False" if should_use_async_extension else "True", + ) + + # remove the built in strawberry sentry extension, if present + extensions = [ + extension + for extension in extensions + if extension + not in (StrawberrySentryAsyncExtension, StrawberrySentrySyncExtension) + ] + + # add our extension + extensions.append( + SentryAsyncExtension if should_use_async_extension else SentrySyncExtension + ) + + kwargs["extensions"] = extensions + + return old_schema_init(self, *args, **kwargs) + + Schema.__init__ = _sentry_patched_schema_init + + +class SentryAsyncExtension(SchemaExtension): # type: ignore + def __init__( + self, + *, + execution_context=None, + ): + # type: (Any, Optional[ExecutionContext]) -> None + if execution_context: + self.execution_context = execution_context + + @cached_property + def _resource_name(self): + # type: () -> str + query_hash = self.hash_query(self.execution_context.query) + + if self.execution_context.operation_name: + return "{}:{}".format(self.execution_context.operation_name, query_hash) + + return query_hash + + def hash_query(self, query): + # type: (str) -> str + return hashlib.md5(query.encode("utf-8")).hexdigest() + + def on_operation(self): + # type: () -> Generator[None, None, None] + self._operation_name = self.execution_context.operation_name + + operation_type = "query" + op = OP.GRAPHQL_QUERY + + if self.execution_context.query is None: + self.execution_context.query = "" + + if self.execution_context.query.strip().startswith("mutation"): + operation_type = "mutation" + op = OP.GRAPHQL_MUTATION + elif self.execution_context.query.strip().startswith("subscription"): + operation_type = "subscription" + op = OP.GRAPHQL_SUBSCRIPTION + + description = operation_type + if self._operation_name: + description += " {}".format(self._operation_name) + + sentry_sdk.add_breadcrumb( + category="graphql.operation", + data={ + "operation_name": self._operation_name, + "operation_type": operation_type, + }, + ) + + span = sentry_sdk.get_current_span() + if span: + self.graphql_span = span.start_child( + op=op, + name=description, + origin=StrawberryIntegration.origin, + ) + else: + self.graphql_span = sentry_sdk.start_span( + op=op, + name=description, + origin=StrawberryIntegration.origin, + ) + + self.graphql_span.set_data("graphql.operation.type", operation_type) + self.graphql_span.set_data("graphql.operation.name", self._operation_name) + self.graphql_span.set_data("graphql.document", self.execution_context.query) + self.graphql_span.set_data("graphql.resource_name", self._resource_name) + + yield + + transaction = self.graphql_span.containing_transaction + if transaction and self.execution_context.operation_name: + transaction.name = self.execution_context.operation_name + transaction.source = TRANSACTION_SOURCE_COMPONENT + transaction.op = op + + self.graphql_span.finish() + + def on_validate(self): + # type: () -> Generator[None, None, None] + self.validation_span = self.graphql_span.start_child( + op=OP.GRAPHQL_VALIDATE, + name="validation", + origin=StrawberryIntegration.origin, + ) + + yield + + self.validation_span.finish() + + def on_parse(self): + # type: () -> Generator[None, None, None] + self.parsing_span = self.graphql_span.start_child( + op=OP.GRAPHQL_PARSE, + name="parsing", + origin=StrawberryIntegration.origin, + ) + + yield + + self.parsing_span.finish() + + def should_skip_tracing(self, _next, info): + # type: (Callable[[Any, GraphQLResolveInfo, Any, Any], Any], GraphQLResolveInfo) -> bool + return strawberry_should_skip_tracing(_next, info) + + async def _resolve(self, _next, root, info, *args, **kwargs): + # type: (Callable[[Any, GraphQLResolveInfo, Any, Any], Any], Any, GraphQLResolveInfo, str, Any) -> Any + result = _next(root, info, *args, **kwargs) + + if isawaitable(result): + result = await result + + return result + + async def resolve(self, _next, root, info, *args, **kwargs): + # type: (Callable[[Any, GraphQLResolveInfo, Any, Any], Any], Any, GraphQLResolveInfo, str, Any) -> Any + if self.should_skip_tracing(_next, info): + return await self._resolve(_next, root, info, *args, **kwargs) + + field_path = "{}.{}".format(info.parent_type, info.field_name) + + with self.graphql_span.start_child( + op=OP.GRAPHQL_RESOLVE, + name="resolving {}".format(field_path), + origin=StrawberryIntegration.origin, + ) as span: + span.set_data("graphql.field_name", info.field_name) + span.set_data("graphql.parent_type", info.parent_type.name) + span.set_data("graphql.field_path", field_path) + span.set_data("graphql.path", ".".join(map(str, info.path.as_list()))) + + return await self._resolve(_next, root, info, *args, **kwargs) + + +class SentrySyncExtension(SentryAsyncExtension): + def resolve(self, _next, root, info, *args, **kwargs): + # type: (Callable[[Any, Any, Any, Any], Any], Any, GraphQLResolveInfo, str, Any) -> Any + if self.should_skip_tracing(_next, info): + return _next(root, info, *args, **kwargs) + + field_path = "{}.{}".format(info.parent_type, info.field_name) + + with self.graphql_span.start_child( + op=OP.GRAPHQL_RESOLVE, + name="resolving {}".format(field_path), + origin=StrawberryIntegration.origin, + ) as span: + span.set_data("graphql.field_name", info.field_name) + span.set_data("graphql.parent_type", info.parent_type.name) + span.set_data("graphql.field_path", field_path) + span.set_data("graphql.path", ".".join(map(str, info.path.as_list()))) + + return _next(root, info, *args, **kwargs) + + +def _patch_execute(): + # type: () -> None + old_execute_async = strawberry_schema.execute + old_execute_sync = strawberry_schema.execute_sync + + async def _sentry_patched_execute_async(*args, **kwargs): + # type: (Any, Any) -> Union[ExecutionResult, SubscriptionExecutionResult] + result = await old_execute_async(*args, **kwargs) + + if sentry_sdk.get_client().get_integration(StrawberryIntegration) is None: + return result + + if "execution_context" in kwargs: + scope = sentry_sdk.get_isolation_scope() + event_processor = _make_request_event_processor(kwargs["execution_context"]) + scope.add_event_processor(event_processor) + + return result + + @ensure_integration_enabled(StrawberryIntegration, old_execute_sync) + def _sentry_patched_execute_sync(*args, **kwargs): + # type: (Any, Any) -> ExecutionResult + result = old_execute_sync(*args, **kwargs) + + if "execution_context" in kwargs: + scope = sentry_sdk.get_isolation_scope() + event_processor = _make_request_event_processor(kwargs["execution_context"]) + scope.add_event_processor(event_processor) + + return result + + strawberry_schema.execute = _sentry_patched_execute_async + strawberry_schema.execute_sync = _sentry_patched_execute_sync + + +def _patch_views(): + # type: () -> None + old_async_view_handle_errors = async_base_view.AsyncBaseHTTPView._handle_errors + old_sync_view_handle_errors = sync_base_view.SyncBaseHTTPView._handle_errors + + def _sentry_patched_async_view_handle_errors(self, errors, response_data): + # type: (Any, List[GraphQLError], GraphQLHTTPResponse) -> None + old_async_view_handle_errors(self, errors, response_data) + _sentry_patched_handle_errors(self, errors, response_data) + + def _sentry_patched_sync_view_handle_errors(self, errors, response_data): + # type: (Any, List[GraphQLError], GraphQLHTTPResponse) -> None + old_sync_view_handle_errors(self, errors, response_data) + _sentry_patched_handle_errors(self, errors, response_data) + + @ensure_integration_enabled(StrawberryIntegration) + def _sentry_patched_handle_errors(self, errors, response_data): + # type: (Any, List[GraphQLError], GraphQLHTTPResponse) -> None + if not errors: + return + + scope = sentry_sdk.get_isolation_scope() + event_processor = _make_response_event_processor(response_data) + scope.add_event_processor(event_processor) + + with capture_internal_exceptions(): + for error in errors: + event, hint = event_from_exception( + error, + client_options=sentry_sdk.get_client().options, + mechanism={ + "type": StrawberryIntegration.identifier, + "handled": False, + }, + ) + sentry_sdk.capture_event(event, hint=hint) + + async_base_view.AsyncBaseHTTPView._handle_errors = ( + _sentry_patched_async_view_handle_errors + ) + sync_base_view.SyncBaseHTTPView._handle_errors = ( + _sentry_patched_sync_view_handle_errors + ) + + +def _make_request_event_processor(execution_context): + # type: (ExecutionContext) -> EventProcessor + + def inner(event, hint): + # type: (Event, dict[str, Any]) -> Event + with capture_internal_exceptions(): + if should_send_default_pii(): + request_data = event.setdefault("request", {}) + request_data["api_target"] = "graphql" + + if not request_data.get("data"): + data = {"query": execution_context.query} + + if execution_context.variables: + data["variables"] = execution_context.variables + if execution_context.operation_name: + data["operationName"] = execution_context.operation_name + + request_data["data"] = data + + else: + try: + del event["request"]["data"] + except (KeyError, TypeError): + pass + + return event + + return inner + + +def _make_response_event_processor(response_data): + # type: (GraphQLHTTPResponse) -> EventProcessor + + def inner(event, hint): + # type: (Event, dict[str, Any]) -> Event + with capture_internal_exceptions(): + if should_send_default_pii(): + contexts = event.setdefault("contexts", {}) + contexts["response"] = {"data": response_data} + + return event + + return inner + + +def _guess_if_using_async(extensions): + # type: (List[SchemaExtension]) -> bool + if StrawberrySentryAsyncExtension in extensions: + return True + elif StrawberrySentrySyncExtension in extensions: + return False + + return bool( + {"starlette", "starlite", "litestar", "fastapi"} & set(_get_installed_modules()) + ) diff --git a/aws/lambda_demo/sentry_sdk/integrations/sys_exit.py b/aws/lambda_demo/sentry_sdk/integrations/sys_exit.py new file mode 100644 index 000000000..2341e1135 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/sys_exit.py @@ -0,0 +1,70 @@ +import functools +import sys + +import sentry_sdk +from sentry_sdk.utils import capture_internal_exceptions, event_from_exception +from sentry_sdk.integrations import Integration +from sentry_sdk._types import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Callable + from typing import NoReturn, Union + + +class SysExitIntegration(Integration): + """Captures sys.exit calls and sends them as events to Sentry. + + By default, SystemExit exceptions are not captured by the SDK. Enabling this integration will capture SystemExit + exceptions generated by sys.exit calls and send them to Sentry. + + This integration, in its default configuration, only captures the sys.exit call if the exit code is a non-zero and + non-None value (unsuccessful exits). Pass `capture_successful_exits=True` to capture successful exits as well. + Note that the integration does not capture SystemExit exceptions raised outside a call to sys.exit. + """ + + identifier = "sys_exit" + + def __init__(self, *, capture_successful_exits=False): + # type: (bool) -> None + self._capture_successful_exits = capture_successful_exits + + @staticmethod + def setup_once(): + # type: () -> None + SysExitIntegration._patch_sys_exit() + + @staticmethod + def _patch_sys_exit(): + # type: () -> None + old_exit = sys.exit # type: Callable[[Union[str, int, None]], NoReturn] + + @functools.wraps(old_exit) + def sentry_patched_exit(__status=0): + # type: (Union[str, int, None]) -> NoReturn + # @ensure_integration_enabled ensures that this is non-None + integration = sentry_sdk.get_client().get_integration(SysExitIntegration) + if integration is None: + old_exit(__status) + + try: + old_exit(__status) + except SystemExit as e: + with capture_internal_exceptions(): + if integration._capture_successful_exits or __status not in ( + 0, + None, + ): + _capture_exception(e) + raise e + + sys.exit = sentry_patched_exit + + +def _capture_exception(exc): + # type: (SystemExit) -> None + event, hint = event_from_exception( + exc, + client_options=sentry_sdk.get_client().options, + mechanism={"type": SysExitIntegration.identifier, "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) diff --git a/aws/lambda_demo/sentry_sdk/integrations/threading.py b/aws/lambda_demo/sentry_sdk/integrations/threading.py new file mode 100644 index 000000000..5de736e23 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/threading.py @@ -0,0 +1,121 @@ +import sys +from functools import wraps +from threading import Thread, current_thread + +import sentry_sdk +from sentry_sdk.integrations import Integration +from sentry_sdk.scope import use_isolation_scope, use_scope +from sentry_sdk.utils import ( + event_from_exception, + capture_internal_exceptions, + logger, + reraise, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import TypeVar + from typing import Callable + from typing import Optional + + from sentry_sdk._types import ExcInfo + + F = TypeVar("F", bound=Callable[..., Any]) + + +class ThreadingIntegration(Integration): + identifier = "threading" + + def __init__(self, propagate_hub=None, propagate_scope=True): + # type: (Optional[bool], bool) -> None + if propagate_hub is not None: + logger.warning( + "Deprecated: propagate_hub is deprecated. This will be removed in the future." + ) + + # Note: propagate_hub did not have any effect on propagation of scope data + # scope data was always propagated no matter what the value of propagate_hub was + # This is why the default for propagate_scope is True + + self.propagate_scope = propagate_scope + + if propagate_hub is not None: + self.propagate_scope = propagate_hub + + @staticmethod + def setup_once(): + # type: () -> None + old_start = Thread.start + + @wraps(old_start) + def sentry_start(self, *a, **kw): + # type: (Thread, *Any, **Any) -> Any + integration = sentry_sdk.get_client().get_integration(ThreadingIntegration) + if integration is None: + return old_start(self, *a, **kw) + + if integration.propagate_scope: + isolation_scope = sentry_sdk.get_isolation_scope() + current_scope = sentry_sdk.get_current_scope() + else: + isolation_scope = None + current_scope = None + + # Patching instance methods in `start()` creates a reference cycle if + # done in a naive way. See + # https://github.com/getsentry/sentry-python/pull/434 + # + # In threading module, using current_thread API will access current thread instance + # without holding it to avoid a reference cycle in an easier way. + with capture_internal_exceptions(): + new_run = _wrap_run( + isolation_scope, + current_scope, + getattr(self.run, "__func__", self.run), + ) + self.run = new_run # type: ignore + + return old_start(self, *a, **kw) + + Thread.start = sentry_start # type: ignore + + +def _wrap_run(isolation_scope_to_use, current_scope_to_use, old_run_func): + # type: (Optional[sentry_sdk.Scope], Optional[sentry_sdk.Scope], F) -> F + @wraps(old_run_func) + def run(*a, **kw): + # type: (*Any, **Any) -> Any + def _run_old_run_func(): + # type: () -> Any + try: + self = current_thread() + return old_run_func(self, *a, **kw) + except Exception: + reraise(*_capture_exception()) + + if isolation_scope_to_use is not None and current_scope_to_use is not None: + with use_isolation_scope(isolation_scope_to_use): + with use_scope(current_scope_to_use): + return _run_old_run_func() + else: + return _run_old_run_func() + + return run # type: ignore + + +def _capture_exception(): + # type: () -> ExcInfo + exc_info = sys.exc_info() + + client = sentry_sdk.get_client() + if client.get_integration(ThreadingIntegration) is not None: + event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "threading", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + return exc_info diff --git a/aws/lambda_demo/sentry_sdk/integrations/tornado.py b/aws/lambda_demo/sentry_sdk/integrations/tornado.py new file mode 100644 index 000000000..b9e465c7c --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/tornado.py @@ -0,0 +1,223 @@ +import weakref +import contextlib +from inspect import iscoroutinefunction + +import sentry_sdk +from sentry_sdk.api import continue_trace +from sentry_sdk.consts import OP +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.tracing import ( + TRANSACTION_SOURCE_COMPONENT, + TRANSACTION_SOURCE_ROUTE, +) +from sentry_sdk.utils import ( + HAS_REAL_CONTEXTVARS, + CONTEXTVARS_ERROR_MESSAGE, + ensure_integration_enabled, + event_from_exception, + capture_internal_exceptions, + transaction_from_function, +) +from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable +from sentry_sdk.integrations._wsgi_common import ( + RequestExtractor, + _filter_headers, + _is_json_content_type, +) +from sentry_sdk.integrations.logging import ignore_logger + +try: + from tornado import version_info as TORNADO_VERSION + from tornado.web import RequestHandler, HTTPError + from tornado.gen import coroutine +except ImportError: + raise DidNotEnable("Tornado not installed") + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Optional + from typing import Dict + from typing import Callable + from typing import Generator + + from sentry_sdk._types import Event, EventProcessor + + +class TornadoIntegration(Integration): + identifier = "tornado" + origin = f"auto.http.{identifier}" + + @staticmethod + def setup_once(): + # type: () -> None + _check_minimum_version(TornadoIntegration, TORNADO_VERSION) + + if not HAS_REAL_CONTEXTVARS: + # Tornado is async. We better have contextvars or we're going to leak + # state between requests. + raise DidNotEnable( + "The tornado integration for Sentry requires Python 3.7+ or the aiocontextvars package" + + CONTEXTVARS_ERROR_MESSAGE + ) + + ignore_logger("tornado.access") + + old_execute = RequestHandler._execute + + awaitable = iscoroutinefunction(old_execute) + + if awaitable: + # Starting Tornado 6 RequestHandler._execute method is a standard Python coroutine (async/await) + # In that case our method should be a coroutine function too + async def sentry_execute_request_handler(self, *args, **kwargs): + # type: (RequestHandler, *Any, **Any) -> Any + with _handle_request_impl(self): + return await old_execute(self, *args, **kwargs) + + else: + + @coroutine # type: ignore + def sentry_execute_request_handler(self, *args, **kwargs): + # type: (RequestHandler, *Any, **Any) -> Any + with _handle_request_impl(self): + result = yield from old_execute(self, *args, **kwargs) + return result + + RequestHandler._execute = sentry_execute_request_handler + + old_log_exception = RequestHandler.log_exception + + def sentry_log_exception(self, ty, value, tb, *args, **kwargs): + # type: (Any, type, BaseException, Any, *Any, **Any) -> Optional[Any] + _capture_exception(ty, value, tb) + return old_log_exception(self, ty, value, tb, *args, **kwargs) + + RequestHandler.log_exception = sentry_log_exception + + +@contextlib.contextmanager +def _handle_request_impl(self): + # type: (RequestHandler) -> Generator[None, None, None] + integration = sentry_sdk.get_client().get_integration(TornadoIntegration) + + if integration is None: + yield + + weak_handler = weakref.ref(self) + + with sentry_sdk.isolation_scope() as scope: + headers = self.request.headers + + scope.clear_breadcrumbs() + processor = _make_event_processor(weak_handler) + scope.add_event_processor(processor) + + transaction = continue_trace( + headers, + op=OP.HTTP_SERVER, + # Like with all other integrations, this is our + # fallback transaction in case there is no route. + # sentry_urldispatcher_resolve is responsible for + # setting a transaction name later. + name="generic Tornado request", + source=TRANSACTION_SOURCE_ROUTE, + origin=TornadoIntegration.origin, + ) + + with sentry_sdk.start_transaction( + transaction, custom_sampling_context={"tornado_request": self.request} + ): + yield + + +@ensure_integration_enabled(TornadoIntegration) +def _capture_exception(ty, value, tb): + # type: (type, BaseException, Any) -> None + if isinstance(value, HTTPError): + return + + event, hint = event_from_exception( + (ty, value, tb), + client_options=sentry_sdk.get_client().options, + mechanism={"type": "tornado", "handled": False}, + ) + + sentry_sdk.capture_event(event, hint=hint) + + +def _make_event_processor(weak_handler): + # type: (Callable[[], RequestHandler]) -> EventProcessor + def tornado_processor(event, hint): + # type: (Event, dict[str, Any]) -> Event + handler = weak_handler() + if handler is None: + return event + + request = handler.request + + with capture_internal_exceptions(): + method = getattr(handler, handler.request.method.lower()) + event["transaction"] = transaction_from_function(method) or "" + event["transaction_info"] = {"source": TRANSACTION_SOURCE_COMPONENT} + + with capture_internal_exceptions(): + extractor = TornadoRequestExtractor(request) + extractor.extract_into_event(event) + + request_info = event["request"] + + request_info["url"] = "%s://%s%s" % ( + request.protocol, + request.host, + request.path, + ) + + request_info["query_string"] = request.query + request_info["method"] = request.method + request_info["env"] = {"REMOTE_ADDR": request.remote_ip} + request_info["headers"] = _filter_headers(dict(request.headers)) + + with capture_internal_exceptions(): + if handler.current_user and should_send_default_pii(): + event.setdefault("user", {}).setdefault("is_authenticated", True) + + return event + + return tornado_processor + + +class TornadoRequestExtractor(RequestExtractor): + def content_length(self): + # type: () -> int + if self.request.body is None: + return 0 + return len(self.request.body) + + def cookies(self): + # type: () -> Dict[str, str] + return {k: v.value for k, v in self.request.cookies.items()} + + def raw_data(self): + # type: () -> bytes + return self.request.body + + def form(self): + # type: () -> Dict[str, Any] + return { + k: [v.decode("latin1", "replace") for v in vs] + for k, vs in self.request.body_arguments.items() + } + + def is_json(self): + # type: () -> bool + return _is_json_content_type(self.request.headers.get("content-type")) + + def files(self): + # type: () -> Dict[str, Any] + return {k: v[0] for k, v in self.request.files.items() if v} + + def size_of_file(self, file): + # type: (Any) -> int + return len(file.body or ()) diff --git a/aws/lambda_demo/sentry_sdk/integrations/trytond.py b/aws/lambda_demo/sentry_sdk/integrations/trytond.py new file mode 100644 index 000000000..2c44c593a --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/trytond.py @@ -0,0 +1,50 @@ +import sentry_sdk +from sentry_sdk.integrations import Integration +from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware +from sentry_sdk.utils import ensure_integration_enabled, event_from_exception + +from trytond.exceptions import TrytonException # type: ignore +from trytond.wsgi import app # type: ignore + + +# TODO: trytond-worker, trytond-cron and trytond-admin intergations + + +class TrytondWSGIIntegration(Integration): + identifier = "trytond_wsgi" + origin = f"auto.http.{identifier}" + + def __init__(self): # type: () -> None + pass + + @staticmethod + def setup_once(): # type: () -> None + app.wsgi_app = SentryWsgiMiddleware( + app.wsgi_app, + span_origin=TrytondWSGIIntegration.origin, + ) + + @ensure_integration_enabled(TrytondWSGIIntegration) + def error_handler(e): # type: (Exception) -> None + if isinstance(e, TrytonException): + return + else: + client = sentry_sdk.get_client() + event, hint = event_from_exception( + e, + client_options=client.options, + mechanism={"type": "trytond", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + # Expected error handlers signature was changed + # when the error_handler decorator was introduced + # in Tryton-5.4 + if hasattr(app, "error_handler"): + + @app.error_handler + def _(app, request, e): # type: ignore + error_handler(e) + + else: + app.error_handlers.append(error_handler) diff --git a/aws/lambda_demo/sentry_sdk/integrations/typer.py b/aws/lambda_demo/sentry_sdk/integrations/typer.py new file mode 100644 index 000000000..8879d6d0d --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/typer.py @@ -0,0 +1,60 @@ +import sentry_sdk +from sentry_sdk.utils import ( + capture_internal_exceptions, + event_from_exception, +) +from sentry_sdk.integrations import Integration, DidNotEnable + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Callable + from typing import Any + from typing import Type + from typing import Optional + + from types import TracebackType + + Excepthook = Callable[ + [Type[BaseException], BaseException, Optional[TracebackType]], + Any, + ] + +try: + import typer +except ImportError: + raise DidNotEnable("Typer not installed") + + +class TyperIntegration(Integration): + identifier = "typer" + + @staticmethod + def setup_once(): + # type: () -> None + typer.main.except_hook = _make_excepthook(typer.main.except_hook) # type: ignore + + +def _make_excepthook(old_excepthook): + # type: (Excepthook) -> Excepthook + def sentry_sdk_excepthook(type_, value, traceback): + # type: (Type[BaseException], BaseException, Optional[TracebackType]) -> None + integration = sentry_sdk.get_client().get_integration(TyperIntegration) + + # Note: If we replace this with ensure_integration_enabled then + # we break the exceptiongroup backport; + # See: https://github.com/getsentry/sentry-python/issues/3097 + if integration is None: + return old_excepthook(type_, value, traceback) + + with capture_internal_exceptions(): + event, hint = event_from_exception( + (type_, value, traceback), + client_options=sentry_sdk.get_client().options, + mechanism={"type": "typer", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + return old_excepthook(type_, value, traceback) + + return sentry_sdk_excepthook diff --git a/aws/lambda_demo/sentry_sdk/integrations/unleash.py b/aws/lambda_demo/sentry_sdk/integrations/unleash.py new file mode 100644 index 000000000..c7108394d --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/unleash.py @@ -0,0 +1,34 @@ +from functools import wraps +from typing import Any + +import sentry_sdk +from sentry_sdk.integrations import Integration, DidNotEnable + +try: + from UnleashClient import UnleashClient +except ImportError: + raise DidNotEnable("UnleashClient is not installed") + + +class UnleashIntegration(Integration): + identifier = "unleash" + + @staticmethod + def setup_once(): + # type: () -> None + # Wrap and patch evaluation methods (instance methods) + old_is_enabled = UnleashClient.is_enabled + + @wraps(old_is_enabled) + def sentry_is_enabled(self, feature, *args, **kwargs): + # type: (UnleashClient, str, *Any, **Any) -> Any + enabled = old_is_enabled(self, feature, *args, **kwargs) + + # We have no way of knowing what type of unleash feature this is, so we have to treat + # it as a boolean / toggle feature. + flags = sentry_sdk.get_current_scope().flags + flags.set(feature, enabled) + + return enabled + + UnleashClient.is_enabled = sentry_is_enabled # type: ignore diff --git a/aws/lambda_demo/sentry_sdk/integrations/wsgi.py b/aws/lambda_demo/sentry_sdk/integrations/wsgi.py new file mode 100644 index 000000000..50deae10c --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/integrations/wsgi.py @@ -0,0 +1,310 @@ +import sys +from functools import partial + +import sentry_sdk +from sentry_sdk._werkzeug import get_host, _get_headers +from sentry_sdk.api import continue_trace +from sentry_sdk.consts import OP +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.integrations._wsgi_common import ( + DEFAULT_HTTP_METHODS_TO_CAPTURE, + _filter_headers, + nullcontext, +) +from sentry_sdk.sessions import track_session +from sentry_sdk.scope import use_isolation_scope +from sentry_sdk.tracing import Transaction, TRANSACTION_SOURCE_ROUTE +from sentry_sdk.utils import ( + ContextVar, + capture_internal_exceptions, + event_from_exception, + reraise, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Callable + from typing import Dict + from typing import Iterator + from typing import Any + from typing import Tuple + from typing import Optional + from typing import TypeVar + from typing import Protocol + + from sentry_sdk.utils import ExcInfo + from sentry_sdk._types import Event, EventProcessor + + WsgiResponseIter = TypeVar("WsgiResponseIter") + WsgiResponseHeaders = TypeVar("WsgiResponseHeaders") + WsgiExcInfo = TypeVar("WsgiExcInfo") + + class StartResponse(Protocol): + def __call__(self, status, response_headers, exc_info=None): # type: ignore + # type: (str, WsgiResponseHeaders, Optional[WsgiExcInfo]) -> WsgiResponseIter + pass + + +_wsgi_middleware_applied = ContextVar("sentry_wsgi_middleware_applied") + + +def wsgi_decoding_dance(s, charset="utf-8", errors="replace"): + # type: (str, str, str) -> str + return s.encode("latin1").decode(charset, errors) + + +def get_request_url(environ, use_x_forwarded_for=False): + # type: (Dict[str, str], bool) -> str + """Return the absolute URL without query string for the given WSGI + environment.""" + script_name = environ.get("SCRIPT_NAME", "").rstrip("/") + path_info = environ.get("PATH_INFO", "").lstrip("/") + path = f"{script_name}/{path_info}" + + return "%s://%s/%s" % ( + environ.get("wsgi.url_scheme"), + get_host(environ, use_x_forwarded_for), + wsgi_decoding_dance(path).lstrip("/"), + ) + + +class SentryWsgiMiddleware: + __slots__ = ( + "app", + "use_x_forwarded_for", + "span_origin", + "http_methods_to_capture", + ) + + def __init__( + self, + app, # type: Callable[[Dict[str, str], Callable[..., Any]], Any] + use_x_forwarded_for=False, # type: bool + span_origin="manual", # type: str + http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: Tuple[str, ...] + ): + # type: (...) -> None + self.app = app + self.use_x_forwarded_for = use_x_forwarded_for + self.span_origin = span_origin + self.http_methods_to_capture = http_methods_to_capture + + def __call__(self, environ, start_response): + # type: (Dict[str, str], Callable[..., Any]) -> _ScopedResponse + if _wsgi_middleware_applied.get(False): + return self.app(environ, start_response) + + _wsgi_middleware_applied.set(True) + try: + with sentry_sdk.isolation_scope() as scope: + with track_session(scope, session_mode="request"): + with capture_internal_exceptions(): + scope.clear_breadcrumbs() + scope._name = "wsgi" + scope.add_event_processor( + _make_wsgi_event_processor( + environ, self.use_x_forwarded_for + ) + ) + + method = environ.get("REQUEST_METHOD", "").upper() + transaction = None + if method in self.http_methods_to_capture: + transaction = continue_trace( + environ, + op=OP.HTTP_SERVER, + name="generic WSGI request", + source=TRANSACTION_SOURCE_ROUTE, + origin=self.span_origin, + ) + + with ( + sentry_sdk.start_transaction( + transaction, + custom_sampling_context={"wsgi_environ": environ}, + ) + if transaction is not None + else nullcontext() + ): + try: + response = self.app( + environ, + partial( + _sentry_start_response, start_response, transaction + ), + ) + except BaseException: + reraise(*_capture_exception()) + finally: + _wsgi_middleware_applied.set(False) + + return _ScopedResponse(scope, response) + + +def _sentry_start_response( # type: ignore + old_start_response, # type: StartResponse + transaction, # type: Optional[Transaction] + status, # type: str + response_headers, # type: WsgiResponseHeaders + exc_info=None, # type: Optional[WsgiExcInfo] +): + # type: (...) -> WsgiResponseIter + with capture_internal_exceptions(): + status_int = int(status.split(" ", 1)[0]) + if transaction is not None: + transaction.set_http_status(status_int) + + if exc_info is None: + # The Django Rest Framework WSGI test client, and likely other + # (incorrect) implementations, cannot deal with the exc_info argument + # if one is present. Avoid providing a third argument if not necessary. + return old_start_response(status, response_headers) + else: + return old_start_response(status, response_headers, exc_info) + + +def _get_environ(environ): + # type: (Dict[str, str]) -> Iterator[Tuple[str, str]] + """ + Returns our explicitly included environment variables we want to + capture (server name, port and remote addr if pii is enabled). + """ + keys = ["SERVER_NAME", "SERVER_PORT"] + if should_send_default_pii(): + # make debugging of proxy setup easier. Proxy headers are + # in headers. + keys += ["REMOTE_ADDR"] + + for key in keys: + if key in environ: + yield key, environ[key] + + +def get_client_ip(environ): + # type: (Dict[str, str]) -> Optional[Any] + """ + Infer the user IP address from various headers. This cannot be used in + security sensitive situations since the value may be forged from a client, + but it's good enough for the event payload. + """ + try: + return environ["HTTP_X_FORWARDED_FOR"].split(",")[0].strip() + except (KeyError, IndexError): + pass + + try: + return environ["HTTP_X_REAL_IP"] + except KeyError: + pass + + return environ.get("REMOTE_ADDR") + + +def _capture_exception(): + # type: () -> ExcInfo + """ + Captures the current exception and sends it to Sentry. + Returns the ExcInfo tuple to it can be reraised afterwards. + """ + exc_info = sys.exc_info() + e = exc_info[1] + + # SystemExit(0) is the only uncaught exception that is expected behavior + should_skip_capture = isinstance(e, SystemExit) and e.code in (0, None) + if not should_skip_capture: + event, hint = event_from_exception( + exc_info, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "wsgi", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + + return exc_info + + +class _ScopedResponse: + """ + Users a separate scope for each response chunk. + + This will make WSGI apps more tolerant against: + - WSGI servers streaming responses from a different thread/from + different threads than the one that called start_response + - close() not being called + - WSGI servers streaming responses interleaved from the same thread + """ + + __slots__ = ("_response", "_scope") + + def __init__(self, scope, response): + # type: (sentry_sdk.scope.Scope, Iterator[bytes]) -> None + self._scope = scope + self._response = response + + def __iter__(self): + # type: () -> Iterator[bytes] + iterator = iter(self._response) + + while True: + with use_isolation_scope(self._scope): + try: + chunk = next(iterator) + except StopIteration: + break + except BaseException: + reraise(*_capture_exception()) + + yield chunk + + def close(self): + # type: () -> None + with use_isolation_scope(self._scope): + try: + self._response.close() # type: ignore + except AttributeError: + pass + except BaseException: + reraise(*_capture_exception()) + + +def _make_wsgi_event_processor(environ, use_x_forwarded_for): + # type: (Dict[str, str], bool) -> EventProcessor + # It's a bit unfortunate that we have to extract and parse the request data + # from the environ so eagerly, but there are a few good reasons for this. + # + # We might be in a situation where the scope never gets torn down + # properly. In that case we will have an unnecessary strong reference to + # all objects in the environ (some of which may take a lot of memory) when + # we're really just interested in a few of them. + # + # Keeping the environment around for longer than the request lifecycle is + # also not necessarily something uWSGI can deal with: + # https://github.com/unbit/uwsgi/issues/1950 + + client_ip = get_client_ip(environ) + request_url = get_request_url(environ, use_x_forwarded_for) + query_string = environ.get("QUERY_STRING") + method = environ.get("REQUEST_METHOD") + env = dict(_get_environ(environ)) + headers = _filter_headers(dict(_get_headers(environ))) + + def event_processor(event, hint): + # type: (Event, Dict[str, Any]) -> Event + with capture_internal_exceptions(): + # if the code below fails halfway through we at least have some data + request_info = event.setdefault("request", {}) + + if should_send_default_pii(): + user_info = event.setdefault("user", {}) + if client_ip: + user_info.setdefault("ip_address", client_ip) + + request_info["url"] = request_url + request_info["query_string"] = query_string + request_info["method"] = method + request_info["env"] = env + request_info["headers"] = headers + + return event + + return event_processor diff --git a/aws/lambda_demo/sentry_sdk/metrics.py b/aws/lambda_demo/sentry_sdk/metrics.py new file mode 100644 index 000000000..f6e9fd6bd --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/metrics.py @@ -0,0 +1,970 @@ +import io +import os +import random +import re +import sys +import threading +import time +import warnings +import zlib +from abc import ABC, abstractmethod +from contextlib import contextmanager +from datetime import datetime, timezone +from functools import wraps, partial + +import sentry_sdk +from sentry_sdk.utils import ( + ContextVar, + now, + nanosecond_time, + to_timestamp, + serialize_frame, + json_dumps, +) +from sentry_sdk.envelope import Envelope, Item +from sentry_sdk.tracing import ( + TRANSACTION_SOURCE_ROUTE, + TRANSACTION_SOURCE_VIEW, + TRANSACTION_SOURCE_COMPONENT, + TRANSACTION_SOURCE_TASK, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import Dict + from typing import Generator + from typing import Iterable + from typing import List + from typing import Optional + from typing import Set + from typing import Tuple + from typing import Union + + from sentry_sdk._types import BucketKey + from sentry_sdk._types import DurationUnit + from sentry_sdk._types import FlushedMetricValue + from sentry_sdk._types import MeasurementUnit + from sentry_sdk._types import MetricMetaKey + from sentry_sdk._types import MetricTagValue + from sentry_sdk._types import MetricTags + from sentry_sdk._types import MetricTagsInternal + from sentry_sdk._types import MetricType + from sentry_sdk._types import MetricValue + + +warnings.warn( + "The sentry_sdk.metrics module is deprecated and will be removed in the next major release. " + "Sentry will reject all metrics sent after October 7, 2024. " + "Learn more: https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Upcoming-API-Changes-to-Metrics", + DeprecationWarning, + stacklevel=2, +) + +_in_metrics = ContextVar("in_metrics", default=False) +_set = set # set is shadowed below + +GOOD_TRANSACTION_SOURCES = frozenset( + [ + TRANSACTION_SOURCE_ROUTE, + TRANSACTION_SOURCE_VIEW, + TRANSACTION_SOURCE_COMPONENT, + TRANSACTION_SOURCE_TASK, + ] +) + +_sanitize_unit = partial(re.compile(r"[^a-zA-Z0-9_]+").sub, "") +_sanitize_metric_key = partial(re.compile(r"[^a-zA-Z0-9_\-.]+").sub, "_") +_sanitize_tag_key = partial(re.compile(r"[^a-zA-Z0-9_\-.\/]+").sub, "") + + +def _sanitize_tag_value(value): + # type: (str) -> str + table = str.maketrans( + { + "\n": "\\n", + "\r": "\\r", + "\t": "\\t", + "\\": "\\\\", + "|": "\\u{7c}", + ",": "\\u{2c}", + } + ) + return value.translate(table) + + +def get_code_location(stacklevel): + # type: (int) -> Optional[Dict[str, Any]] + try: + frm = sys._getframe(stacklevel) + except Exception: + return None + + return serialize_frame( + frm, include_local_variables=False, include_source_context=True + ) + + +@contextmanager +def recursion_protection(): + # type: () -> Generator[bool, None, None] + """Enters recursion protection and returns the old flag.""" + old_in_metrics = _in_metrics.get() + _in_metrics.set(True) + try: + yield old_in_metrics + finally: + _in_metrics.set(old_in_metrics) + + +def metrics_noop(func): + # type: (Any) -> Any + """Convenient decorator that uses `recursion_protection` to + make a function a noop. + """ + + @wraps(func) + def new_func(*args, **kwargs): + # type: (*Any, **Any) -> Any + with recursion_protection() as in_metrics: + if not in_metrics: + return func(*args, **kwargs) + + return new_func + + +class Metric(ABC): + __slots__ = () + + @abstractmethod + def __init__(self, first): + # type: (MetricValue) -> None + pass + + @property + @abstractmethod + def weight(self): + # type: () -> int + pass + + @abstractmethod + def add(self, value): + # type: (MetricValue) -> None + pass + + @abstractmethod + def serialize_value(self): + # type: () -> Iterable[FlushedMetricValue] + pass + + +class CounterMetric(Metric): + __slots__ = ("value",) + + def __init__( + self, first # type: MetricValue + ): + # type: (...) -> None + self.value = float(first) + + @property + def weight(self): + # type: (...) -> int + return 1 + + def add( + self, value # type: MetricValue + ): + # type: (...) -> None + self.value += float(value) + + def serialize_value(self): + # type: (...) -> Iterable[FlushedMetricValue] + return (self.value,) + + +class GaugeMetric(Metric): + __slots__ = ( + "last", + "min", + "max", + "sum", + "count", + ) + + def __init__( + self, first # type: MetricValue + ): + # type: (...) -> None + first = float(first) + self.last = first + self.min = first + self.max = first + self.sum = first + self.count = 1 + + @property + def weight(self): + # type: (...) -> int + # Number of elements. + return 5 + + def add( + self, value # type: MetricValue + ): + # type: (...) -> None + value = float(value) + self.last = value + self.min = min(self.min, value) + self.max = max(self.max, value) + self.sum += value + self.count += 1 + + def serialize_value(self): + # type: (...) -> Iterable[FlushedMetricValue] + return ( + self.last, + self.min, + self.max, + self.sum, + self.count, + ) + + +class DistributionMetric(Metric): + __slots__ = ("value",) + + def __init__( + self, first # type: MetricValue + ): + # type(...) -> None + self.value = [float(first)] + + @property + def weight(self): + # type: (...) -> int + return len(self.value) + + def add( + self, value # type: MetricValue + ): + # type: (...) -> None + self.value.append(float(value)) + + def serialize_value(self): + # type: (...) -> Iterable[FlushedMetricValue] + return self.value + + +class SetMetric(Metric): + __slots__ = ("value",) + + def __init__( + self, first # type: MetricValue + ): + # type: (...) -> None + self.value = {first} + + @property + def weight(self): + # type: (...) -> int + return len(self.value) + + def add( + self, value # type: MetricValue + ): + # type: (...) -> None + self.value.add(value) + + def serialize_value(self): + # type: (...) -> Iterable[FlushedMetricValue] + def _hash(x): + # type: (MetricValue) -> int + if isinstance(x, str): + return zlib.crc32(x.encode("utf-8")) & 0xFFFFFFFF + return int(x) + + return (_hash(value) for value in self.value) + + +def _encode_metrics(flushable_buckets): + # type: (Iterable[Tuple[int, Dict[BucketKey, Metric]]]) -> bytes + out = io.BytesIO() + _write = out.write + + # Note on sanitization: we intentionally sanitize in emission (serialization) + # and not during aggregation for performance reasons. This means that the + # envelope can in fact have duplicate buckets stored. This is acceptable for + # relay side emission and should not happen commonly. + + for timestamp, buckets in flushable_buckets: + for bucket_key, metric in buckets.items(): + metric_type, metric_name, metric_unit, metric_tags = bucket_key + metric_name = _sanitize_metric_key(metric_name) + metric_unit = _sanitize_unit(metric_unit) + _write(metric_name.encode("utf-8")) + _write(b"@") + _write(metric_unit.encode("utf-8")) + + for serialized_value in metric.serialize_value(): + _write(b":") + _write(str(serialized_value).encode("utf-8")) + + _write(b"|") + _write(metric_type.encode("ascii")) + + if metric_tags: + _write(b"|#") + first = True + for tag_key, tag_value in metric_tags: + tag_key = _sanitize_tag_key(tag_key) + if not tag_key: + continue + if first: + first = False + else: + _write(b",") + _write(tag_key.encode("utf-8")) + _write(b":") + _write(_sanitize_tag_value(tag_value).encode("utf-8")) + + _write(b"|T") + _write(str(timestamp).encode("ascii")) + _write(b"\n") + + return out.getvalue() + + +def _encode_locations(timestamp, code_locations): + # type: (int, Iterable[Tuple[MetricMetaKey, Dict[str, Any]]]) -> bytes + mapping = {} # type: Dict[str, List[Any]] + + for key, loc in code_locations: + metric_type, name, unit = key + mri = "{}:{}@{}".format( + metric_type, _sanitize_metric_key(name), _sanitize_unit(unit) + ) + + loc["type"] = "location" + mapping.setdefault(mri, []).append(loc) + + return json_dumps({"timestamp": timestamp, "mapping": mapping}) + + +METRIC_TYPES = { + "c": CounterMetric, + "g": GaugeMetric, + "d": DistributionMetric, + "s": SetMetric, +} # type: dict[MetricType, type[Metric]] + +# some of these are dumb +TIMING_FUNCTIONS = { + "nanosecond": nanosecond_time, + "microsecond": lambda: nanosecond_time() / 1000.0, + "millisecond": lambda: nanosecond_time() / 1000000.0, + "second": now, + "minute": lambda: now() / 60.0, + "hour": lambda: now() / 3600.0, + "day": lambda: now() / 3600.0 / 24.0, + "week": lambda: now() / 3600.0 / 24.0 / 7.0, +} + + +class LocalAggregator: + __slots__ = ("_measurements",) + + def __init__(self): + # type: (...) -> None + self._measurements = ( + {} + ) # type: Dict[Tuple[str, MetricTagsInternal], Tuple[float, float, int, float]] + + def add( + self, + ty, # type: MetricType + key, # type: str + value, # type: float + unit, # type: MeasurementUnit + tags, # type: MetricTagsInternal + ): + # type: (...) -> None + export_key = "%s:%s@%s" % (ty, key, unit) + bucket_key = (export_key, tags) + + old = self._measurements.get(bucket_key) + if old is not None: + v_min, v_max, v_count, v_sum = old + v_min = min(v_min, value) + v_max = max(v_max, value) + v_count += 1 + v_sum += value + else: + v_min = v_max = v_sum = value + v_count = 1 + self._measurements[bucket_key] = (v_min, v_max, v_count, v_sum) + + def to_json(self): + # type: (...) -> Dict[str, Any] + rv = {} # type: Any + for (export_key, tags), ( + v_min, + v_max, + v_count, + v_sum, + ) in self._measurements.items(): + rv.setdefault(export_key, []).append( + { + "tags": _tags_to_dict(tags), + "min": v_min, + "max": v_max, + "count": v_count, + "sum": v_sum, + } + ) + return rv + + +class MetricsAggregator: + ROLLUP_IN_SECONDS = 10.0 + MAX_WEIGHT = 100000 + FLUSHER_SLEEP_TIME = 5.0 + + def __init__( + self, + capture_func, # type: Callable[[Envelope], None] + enable_code_locations=False, # type: bool + ): + # type: (...) -> None + self.buckets = {} # type: Dict[int, Any] + self._enable_code_locations = enable_code_locations + self._seen_locations = _set() # type: Set[Tuple[int, MetricMetaKey]] + self._pending_locations = {} # type: Dict[int, List[Tuple[MetricMetaKey, Any]]] + self._buckets_total_weight = 0 + self._capture_func = capture_func + self._running = True + self._lock = threading.Lock() + + self._flush_event = threading.Event() # type: threading.Event + self._force_flush = False + + # The aggregator shifts its flushing by up to an entire rollup window to + # avoid multiple clients trampling on end of a 10 second window as all the + # buckets are anchored to multiples of ROLLUP seconds. We randomize this + # number once per aggregator boot to achieve some level of offsetting + # across a fleet of deployed SDKs. Relay itself will also apply independent + # jittering. + self._flush_shift = random.random() * self.ROLLUP_IN_SECONDS + + self._flusher = None # type: Optional[threading.Thread] + self._flusher_pid = None # type: Optional[int] + + def _ensure_thread(self): + # type: (...) -> bool + """For forking processes we might need to restart this thread. + This ensures that our process actually has that thread running. + """ + if not self._running: + return False + + pid = os.getpid() + if self._flusher_pid == pid: + return True + + with self._lock: + # Recheck to make sure another thread didn't get here and start the + # the flusher in the meantime + if self._flusher_pid == pid: + return True + + self._flusher_pid = pid + + self._flusher = threading.Thread(target=self._flush_loop) + self._flusher.daemon = True + + try: + self._flusher.start() + except RuntimeError: + # Unfortunately at this point the interpreter is in a state that no + # longer allows us to spawn a thread and we have to bail. + self._running = False + return False + + return True + + def _flush_loop(self): + # type: (...) -> None + _in_metrics.set(True) + while self._running or self._force_flush: + if self._running: + self._flush_event.wait(self.FLUSHER_SLEEP_TIME) + self._flush() + + def _flush(self): + # type: (...) -> None + self._emit(self._flushable_buckets(), self._flushable_locations()) + + def _flushable_buckets(self): + # type: (...) -> (Iterable[Tuple[int, Dict[BucketKey, Metric]]]) + with self._lock: + force_flush = self._force_flush + cutoff = time.time() - self.ROLLUP_IN_SECONDS - self._flush_shift + flushable_buckets = () # type: Iterable[Tuple[int, Dict[BucketKey, Metric]]] + weight_to_remove = 0 + + if force_flush: + flushable_buckets = self.buckets.items() + self.buckets = {} + self._buckets_total_weight = 0 + self._force_flush = False + else: + flushable_buckets = [] + for buckets_timestamp, buckets in self.buckets.items(): + # If the timestamp of the bucket is newer that the rollup we want to skip it. + if buckets_timestamp <= cutoff: + flushable_buckets.append((buckets_timestamp, buckets)) + + # We will clear the elements while holding the lock, in order to avoid requesting it downstream again. + for buckets_timestamp, buckets in flushable_buckets: + for metric in buckets.values(): + weight_to_remove += metric.weight + del self.buckets[buckets_timestamp] + + self._buckets_total_weight -= weight_to_remove + + return flushable_buckets + + def _flushable_locations(self): + # type: (...) -> Dict[int, List[Tuple[MetricMetaKey, Dict[str, Any]]]] + with self._lock: + locations = self._pending_locations + self._pending_locations = {} + return locations + + @metrics_noop + def add( + self, + ty, # type: MetricType + key, # type: str + value, # type: MetricValue + unit, # type: MeasurementUnit + tags, # type: Optional[MetricTags] + timestamp=None, # type: Optional[Union[float, datetime]] + local_aggregator=None, # type: Optional[LocalAggregator] + stacklevel=0, # type: Optional[int] + ): + # type: (...) -> None + if not self._ensure_thread() or self._flusher is None: + return None + + if timestamp is None: + timestamp = time.time() + elif isinstance(timestamp, datetime): + timestamp = to_timestamp(timestamp) + + bucket_timestamp = int( + (timestamp // self.ROLLUP_IN_SECONDS) * self.ROLLUP_IN_SECONDS + ) + serialized_tags = _serialize_tags(tags) + bucket_key = ( + ty, + key, + unit, + serialized_tags, + ) + + with self._lock: + local_buckets = self.buckets.setdefault(bucket_timestamp, {}) + metric = local_buckets.get(bucket_key) + if metric is not None: + previous_weight = metric.weight + metric.add(value) + else: + metric = local_buckets[bucket_key] = METRIC_TYPES[ty](value) + previous_weight = 0 + + added = metric.weight - previous_weight + + if stacklevel is not None: + self.record_code_location(ty, key, unit, stacklevel + 2, timestamp) + + # Given the new weight we consider whether we want to force flush. + self._consider_force_flush() + + # For sets, we only record that a value has been added to the set but not which one. + # See develop docs: https://develop.sentry.dev/sdk/metrics/#sets + if local_aggregator is not None: + local_value = float(added if ty == "s" else value) + local_aggregator.add(ty, key, local_value, unit, serialized_tags) + + def record_code_location( + self, + ty, # type: MetricType + key, # type: str + unit, # type: MeasurementUnit + stacklevel, # type: int + timestamp=None, # type: Optional[float] + ): + # type: (...) -> None + if not self._enable_code_locations: + return + if timestamp is None: + timestamp = time.time() + meta_key = (ty, key, unit) + start_of_day = datetime.fromtimestamp(timestamp, timezone.utc).replace( + hour=0, minute=0, second=0, microsecond=0, tzinfo=None + ) + start_of_day = int(to_timestamp(start_of_day)) + + if (start_of_day, meta_key) not in self._seen_locations: + self._seen_locations.add((start_of_day, meta_key)) + loc = get_code_location(stacklevel + 3) + if loc is not None: + # Group metadata by day to make flushing more efficient. + # There needs to be one envelope item per timestamp. + self._pending_locations.setdefault(start_of_day, []).append( + (meta_key, loc) + ) + + @metrics_noop + def need_code_location( + self, + ty, # type: MetricType + key, # type: str + unit, # type: MeasurementUnit + timestamp, # type: float + ): + # type: (...) -> bool + if self._enable_code_locations: + return False + meta_key = (ty, key, unit) + start_of_day = datetime.fromtimestamp(timestamp, timezone.utc).replace( + hour=0, minute=0, second=0, microsecond=0, tzinfo=None + ) + start_of_day = int(to_timestamp(start_of_day)) + return (start_of_day, meta_key) not in self._seen_locations + + def kill(self): + # type: (...) -> None + if self._flusher is None: + return + + self._running = False + self._flush_event.set() + self._flusher = None + + @metrics_noop + def flush(self): + # type: (...) -> None + self._force_flush = True + self._flush() + + def _consider_force_flush(self): + # type: (...) -> None + # It's important to acquire a lock around this method, since it will touch shared data structures. + total_weight = len(self.buckets) + self._buckets_total_weight + if total_weight >= self.MAX_WEIGHT: + self._force_flush = True + self._flush_event.set() + + def _emit( + self, + flushable_buckets, # type: (Iterable[Tuple[int, Dict[BucketKey, Metric]]]) + code_locations, # type: Dict[int, List[Tuple[MetricMetaKey, Dict[str, Any]]]] + ): + # type: (...) -> Optional[Envelope] + envelope = Envelope() + + if flushable_buckets: + encoded_metrics = _encode_metrics(flushable_buckets) + envelope.add_item(Item(payload=encoded_metrics, type="statsd")) + + for timestamp, locations in code_locations.items(): + encoded_locations = _encode_locations(timestamp, locations) + envelope.add_item(Item(payload=encoded_locations, type="metric_meta")) + + if envelope.items: + self._capture_func(envelope) + return envelope + return None + + +def _serialize_tags( + tags, # type: Optional[MetricTags] +): + # type: (...) -> MetricTagsInternal + if not tags: + return () + + rv = [] + for key, value in tags.items(): + # If the value is a collection, we want to flatten it. + if isinstance(value, (list, tuple)): + for inner_value in value: + if inner_value is not None: + rv.append((key, str(inner_value))) + elif value is not None: + rv.append((key, str(value))) + + # It's very important to sort the tags in order to obtain the + # same bucket key. + return tuple(sorted(rv)) + + +def _tags_to_dict(tags): + # type: (MetricTagsInternal) -> Dict[str, Any] + rv = {} # type: Dict[str, Any] + for tag_name, tag_value in tags: + old_value = rv.get(tag_name) + if old_value is not None: + if isinstance(old_value, list): + old_value.append(tag_value) + else: + rv[tag_name] = [old_value, tag_value] + else: + rv[tag_name] = tag_value + return rv + + +def _get_aggregator(): + # type: () -> Optional[MetricsAggregator] + client = sentry_sdk.get_client() + return ( + client.metrics_aggregator + if client.is_active() and client.metrics_aggregator is not None + else None + ) + + +def _get_aggregator_and_update_tags(key, value, unit, tags): + # type: (str, Optional[MetricValue], MeasurementUnit, Optional[MetricTags]) -> Tuple[Optional[MetricsAggregator], Optional[LocalAggregator], Optional[MetricTags]] + client = sentry_sdk.get_client() + if not client.is_active() or client.metrics_aggregator is None: + return None, None, tags + + updated_tags = dict(tags or ()) # type: Dict[str, MetricTagValue] + updated_tags.setdefault("release", client.options["release"]) + updated_tags.setdefault("environment", client.options["environment"]) + + scope = sentry_sdk.get_current_scope() + local_aggregator = None + + # We go with the low-level API here to access transaction information as + # this one is the same between just errors and errors + performance + transaction_source = scope._transaction_info.get("source") + if transaction_source in GOOD_TRANSACTION_SOURCES: + transaction_name = scope._transaction + if transaction_name: + updated_tags.setdefault("transaction", transaction_name) + if scope._span is not None: + local_aggregator = scope._span._get_local_aggregator() + + experiments = client.options.get("_experiments", {}) + before_emit_callback = experiments.get("before_emit_metric") + if before_emit_callback is not None: + with recursion_protection() as in_metrics: + if not in_metrics: + if not before_emit_callback(key, value, unit, updated_tags): + return None, None, updated_tags + + return client.metrics_aggregator, local_aggregator, updated_tags + + +def increment( + key, # type: str + value=1.0, # type: float + unit="none", # type: MeasurementUnit + tags=None, # type: Optional[MetricTags] + timestamp=None, # type: Optional[Union[float, datetime]] + stacklevel=0, # type: int +): + # type: (...) -> None + """Increments a counter.""" + aggregator, local_aggregator, tags = _get_aggregator_and_update_tags( + key, value, unit, tags + ) + if aggregator is not None: + aggregator.add( + "c", key, value, unit, tags, timestamp, local_aggregator, stacklevel + ) + + +# alias as incr is relatively common in python +incr = increment + + +class _Timing: + def __init__( + self, + key, # type: str + tags, # type: Optional[MetricTags] + timestamp, # type: Optional[Union[float, datetime]] + value, # type: Optional[float] + unit, # type: DurationUnit + stacklevel, # type: int + ): + # type: (...) -> None + self.key = key + self.tags = tags + self.timestamp = timestamp + self.value = value + self.unit = unit + self.entered = None # type: Optional[float] + self._span = None # type: Optional[sentry_sdk.tracing.Span] + self.stacklevel = stacklevel + + def _validate_invocation(self, context): + # type: (str) -> None + if self.value is not None: + raise TypeError( + "cannot use timing as %s when a value is provided" % context + ) + + def __enter__(self): + # type: (...) -> _Timing + self.entered = TIMING_FUNCTIONS[self.unit]() + self._validate_invocation("context-manager") + self._span = sentry_sdk.start_span(op="metric.timing", name=self.key) + if self.tags: + for key, value in self.tags.items(): + if isinstance(value, (tuple, list)): + value = ",".join(sorted(map(str, value))) + self._span.set_tag(key, value) + self._span.__enter__() + + # report code locations here for better accuracy + aggregator = _get_aggregator() + if aggregator is not None: + aggregator.record_code_location("d", self.key, self.unit, self.stacklevel) + + return self + + def __exit__(self, exc_type, exc_value, tb): + # type: (Any, Any, Any) -> None + assert self._span, "did not enter" + aggregator, local_aggregator, tags = _get_aggregator_and_update_tags( + self.key, + self.value, + self.unit, + self.tags, + ) + if aggregator is not None: + elapsed = TIMING_FUNCTIONS[self.unit]() - self.entered # type: ignore + aggregator.add( + "d", + self.key, + elapsed, + self.unit, + tags, + self.timestamp, + local_aggregator, + None, # code locations are reported in __enter__ + ) + + self._span.__exit__(exc_type, exc_value, tb) + self._span = None + + def __call__(self, f): + # type: (Any) -> Any + self._validate_invocation("decorator") + + @wraps(f) + def timed_func(*args, **kwargs): + # type: (*Any, **Any) -> Any + with timing( + key=self.key, + tags=self.tags, + timestamp=self.timestamp, + unit=self.unit, + stacklevel=self.stacklevel + 1, + ): + return f(*args, **kwargs) + + return timed_func + + +def timing( + key, # type: str + value=None, # type: Optional[float] + unit="second", # type: DurationUnit + tags=None, # type: Optional[MetricTags] + timestamp=None, # type: Optional[Union[float, datetime]] + stacklevel=0, # type: int +): + # type: (...) -> _Timing + """Emits a distribution with the time it takes to run the given code block. + + This method supports three forms of invocation: + + - when a `value` is provided, it functions similar to `distribution` but with + - it can be used as a context manager + - it can be used as a decorator + """ + if value is not None: + aggregator, local_aggregator, tags = _get_aggregator_and_update_tags( + key, value, unit, tags + ) + if aggregator is not None: + aggregator.add( + "d", key, value, unit, tags, timestamp, local_aggregator, stacklevel + ) + return _Timing(key, tags, timestamp, value, unit, stacklevel) + + +def distribution( + key, # type: str + value, # type: float + unit="none", # type: MeasurementUnit + tags=None, # type: Optional[MetricTags] + timestamp=None, # type: Optional[Union[float, datetime]] + stacklevel=0, # type: int +): + # type: (...) -> None + """Emits a distribution.""" + aggregator, local_aggregator, tags = _get_aggregator_and_update_tags( + key, value, unit, tags + ) + if aggregator is not None: + aggregator.add( + "d", key, value, unit, tags, timestamp, local_aggregator, stacklevel + ) + + +def set( + key, # type: str + value, # type: Union[int, str] + unit="none", # type: MeasurementUnit + tags=None, # type: Optional[MetricTags] + timestamp=None, # type: Optional[Union[float, datetime]] + stacklevel=0, # type: int +): + # type: (...) -> None + """Emits a set.""" + aggregator, local_aggregator, tags = _get_aggregator_and_update_tags( + key, value, unit, tags + ) + if aggregator is not None: + aggregator.add( + "s", key, value, unit, tags, timestamp, local_aggregator, stacklevel + ) + + +def gauge( + key, # type: str + value, # type: float + unit="none", # type: MeasurementUnit + tags=None, # type: Optional[MetricTags] + timestamp=None, # type: Optional[Union[float, datetime]] + stacklevel=0, # type: int +): + # type: (...) -> None + """Emits a gauge.""" + aggregator, local_aggregator, tags = _get_aggregator_and_update_tags( + key, value, unit, tags + ) + if aggregator is not None: + aggregator.add( + "g", key, value, unit, tags, timestamp, local_aggregator, stacklevel + ) diff --git a/aws/lambda_demo/sentry_sdk/monitor.py b/aws/lambda_demo/sentry_sdk/monitor.py new file mode 100644 index 000000000..68d9017bf --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/monitor.py @@ -0,0 +1,124 @@ +import os +import time +from threading import Thread, Lock + +import sentry_sdk +from sentry_sdk.utils import logger + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Optional + + +MAX_DOWNSAMPLE_FACTOR = 10 + + +class Monitor: + """ + Performs health checks in a separate thread once every interval seconds + and updates the internal state. Other parts of the SDK only read this state + and act accordingly. + """ + + name = "sentry.monitor" + + def __init__(self, transport, interval=10): + # type: (sentry_sdk.transport.Transport, float) -> None + self.transport = transport # type: sentry_sdk.transport.Transport + self.interval = interval # type: float + + self._healthy = True + self._downsample_factor = 0 # type: int + + self._thread = None # type: Optional[Thread] + self._thread_lock = Lock() + self._thread_for_pid = None # type: Optional[int] + self._running = True + + def _ensure_running(self): + # type: () -> None + """ + Check that the monitor has an active thread to run in, or create one if not. + + Note that this might fail (e.g. in Python 3.12 it's not possible to + spawn new threads at interpreter shutdown). In that case self._running + will be False after running this function. + """ + if self._thread_for_pid == os.getpid() and self._thread is not None: + return None + + with self._thread_lock: + if self._thread_for_pid == os.getpid() and self._thread is not None: + return None + + def _thread(): + # type: (...) -> None + while self._running: + time.sleep(self.interval) + if self._running: + self.run() + + thread = Thread(name=self.name, target=_thread) + thread.daemon = True + try: + thread.start() + except RuntimeError: + # Unfortunately at this point the interpreter is in a state that no + # longer allows us to spawn a thread and we have to bail. + self._running = False + return None + + self._thread = thread + self._thread_for_pid = os.getpid() + + return None + + def run(self): + # type: () -> None + self.check_health() + self.set_downsample_factor() + + def set_downsample_factor(self): + # type: () -> None + if self._healthy: + if self._downsample_factor > 0: + logger.debug( + "[Monitor] health check positive, reverting to normal sampling" + ) + self._downsample_factor = 0 + else: + if self.downsample_factor < MAX_DOWNSAMPLE_FACTOR: + self._downsample_factor += 1 + logger.debug( + "[Monitor] health check negative, downsampling with a factor of %d", + self._downsample_factor, + ) + + def check_health(self): + # type: () -> None + """ + Perform the actual health checks, + currently only checks if the transport is rate-limited. + TODO: augment in the future with more checks. + """ + self._healthy = self.transport.is_healthy() + + def is_healthy(self): + # type: () -> bool + self._ensure_running() + return self._healthy + + @property + def downsample_factor(self): + # type: () -> int + self._ensure_running() + return self._downsample_factor + + def kill(self): + # type: () -> None + self._running = False + + def __del__(self): + # type: () -> None + self.kill() diff --git a/aws/lambda_demo/sentry_sdk/profiler/__init__.py b/aws/lambda_demo/sentry_sdk/profiler/__init__.py new file mode 100644 index 000000000..46382cc29 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/profiler/__init__.py @@ -0,0 +1,42 @@ +from sentry_sdk.profiler.continuous_profiler import start_profiler, stop_profiler +from sentry_sdk.profiler.transaction_profiler import ( + MAX_PROFILE_DURATION_NS, + PROFILE_MINIMUM_SAMPLES, + Profile, + Scheduler, + ThreadScheduler, + GeventScheduler, + has_profiling_enabled, + setup_profiler, + teardown_profiler, +) +from sentry_sdk.profiler.utils import ( + DEFAULT_SAMPLING_FREQUENCY, + MAX_STACK_DEPTH, + get_frame_name, + extract_frame, + extract_stack, + frame_id, +) + +__all__ = [ + "start_profiler", + "stop_profiler", + # DEPRECATED: The following was re-exported for backwards compatibility. It + # will be removed from sentry_sdk.profiler in a future release. + "MAX_PROFILE_DURATION_NS", + "PROFILE_MINIMUM_SAMPLES", + "Profile", + "Scheduler", + "ThreadScheduler", + "GeventScheduler", + "has_profiling_enabled", + "setup_profiler", + "teardown_profiler", + "DEFAULT_SAMPLING_FREQUENCY", + "MAX_STACK_DEPTH", + "get_frame_name", + "extract_frame", + "extract_stack", + "frame_id", +] diff --git a/aws/lambda_demo/sentry_sdk/profiler/continuous_profiler.py b/aws/lambda_demo/sentry_sdk/profiler/continuous_profiler.py new file mode 100644 index 000000000..5d64896b9 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/profiler/continuous_profiler.py @@ -0,0 +1,538 @@ +import atexit +import os +import sys +import threading +import time +import uuid +from datetime import datetime, timezone + +from sentry_sdk.consts import VERSION +from sentry_sdk.envelope import Envelope +from sentry_sdk._lru_cache import LRUCache +from sentry_sdk.profiler.utils import ( + DEFAULT_SAMPLING_FREQUENCY, + extract_stack, +) +from sentry_sdk.utils import ( + capture_internal_exception, + is_gevent, + logger, + now, + set_in_app_in_frames, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import Dict + from typing import List + from typing import Optional + from typing import Type + from typing import Union + from typing_extensions import TypedDict + from sentry_sdk._types import ContinuousProfilerMode, SDKInfo + from sentry_sdk.profiler.utils import ( + ExtractedSample, + FrameId, + StackId, + ThreadId, + ProcessedFrame, + ProcessedStack, + ) + + ProcessedSample = TypedDict( + "ProcessedSample", + { + "timestamp": float, + "thread_id": ThreadId, + "stack_id": int, + }, + ) + + +try: + from gevent.monkey import get_original + from gevent.threadpool import ThreadPool as _ThreadPool + + ThreadPool = _ThreadPool # type: Optional[Type[_ThreadPool]] + thread_sleep = get_original("time", "sleep") +except ImportError: + thread_sleep = time.sleep + ThreadPool = None + + +_scheduler = None # type: Optional[ContinuousScheduler] + + +def setup_continuous_profiler(options, sdk_info, capture_func): + # type: (Dict[str, Any], SDKInfo, Callable[[Envelope], None]) -> bool + global _scheduler + + if _scheduler is not None: + logger.debug("[Profiling] Continuous Profiler is already setup") + return False + + if is_gevent(): + # If gevent has patched the threading modules then we cannot rely on + # them to spawn a native thread for sampling. + # Instead we default to the GeventContinuousScheduler which is capable of + # spawning native threads within gevent. + default_profiler_mode = GeventContinuousScheduler.mode + else: + default_profiler_mode = ThreadContinuousScheduler.mode + + experiments = options.get("_experiments", {}) + + profiler_mode = ( + experiments.get("continuous_profiling_mode") or default_profiler_mode + ) + + frequency = DEFAULT_SAMPLING_FREQUENCY + + if profiler_mode == ThreadContinuousScheduler.mode: + _scheduler = ThreadContinuousScheduler( + frequency, options, sdk_info, capture_func + ) + elif profiler_mode == GeventContinuousScheduler.mode: + _scheduler = GeventContinuousScheduler( + frequency, options, sdk_info, capture_func + ) + else: + raise ValueError("Unknown continuous profiler mode: {}".format(profiler_mode)) + + logger.debug( + "[Profiling] Setting up continuous profiler in {mode} mode".format( + mode=_scheduler.mode + ) + ) + + atexit.register(teardown_continuous_profiler) + + return True + + +def try_autostart_continuous_profiler(): + # type: () -> None + if _scheduler is None: + return + + # Ensure that the scheduler only autostarts once per process. + # This is necessary because many web servers use forks to spawn + # additional processes. And the profiler is only spawned on the + # master process, then it often only profiles the main process + # and not the ones where the requests are being handled. + # + # Additionally, we only want this autostart behaviour once per + # process. If the user explicitly calls `stop_profiler`, it should + # be respected and not start the profiler again. + if not _scheduler.should_autostart(): + return + + _scheduler.ensure_running() + + +def start_profiler(): + # type: () -> None + if _scheduler is None: + return + + _scheduler.ensure_running() + + +def stop_profiler(): + # type: () -> None + if _scheduler is None: + return + + _scheduler.teardown() + + +def teardown_continuous_profiler(): + # type: () -> None + stop_profiler() + + global _scheduler + _scheduler = None + + +def get_profiler_id(): + # type: () -> Union[str, None] + if _scheduler is None: + return None + return _scheduler.profiler_id + + +class ContinuousScheduler: + mode = "unknown" # type: ContinuousProfilerMode + + def __init__(self, frequency, options, sdk_info, capture_func): + # type: (int, Dict[str, Any], SDKInfo, Callable[[Envelope], None]) -> None + self.interval = 1.0 / frequency + self.options = options + self.sdk_info = sdk_info + self.capture_func = capture_func + self.sampler = self.make_sampler() + self.buffer = None # type: Optional[ProfileBuffer] + + self.running = False + + def should_autostart(self): + # type: () -> bool + experiments = self.options.get("_experiments") + if not experiments: + return False + return experiments.get("continuous_profiling_auto_start") + + def ensure_running(self): + # type: () -> None + raise NotImplementedError + + def teardown(self): + # type: () -> None + raise NotImplementedError + + def pause(self): + # type: () -> None + raise NotImplementedError + + def reset_buffer(self): + # type: () -> None + self.buffer = ProfileBuffer( + self.options, self.sdk_info, PROFILE_BUFFER_SECONDS, self.capture_func + ) + + @property + def profiler_id(self): + # type: () -> Union[str, None] + if self.buffer is None: + return None + return self.buffer.profiler_id + + def make_sampler(self): + # type: () -> Callable[..., None] + cwd = os.getcwd() + + cache = LRUCache(max_size=256) + + def _sample_stack(*args, **kwargs): + # type: (*Any, **Any) -> None + """ + Take a sample of the stack on all the threads in the process. + This should be called at a regular interval to collect samples. + """ + + ts = now() + + try: + sample = [ + (str(tid), extract_stack(frame, cache, cwd)) + for tid, frame in sys._current_frames().items() + ] + except AttributeError: + # For some reason, the frame we get doesn't have certain attributes. + # When this happens, we abandon the current sample as it's bad. + capture_internal_exception(sys.exc_info()) + return + + if self.buffer is not None: + self.buffer.write(ts, sample) + + return _sample_stack + + def run(self): + # type: () -> None + last = time.perf_counter() + + while self.running: + self.sampler() + + # some time may have elapsed since the last time + # we sampled, so we need to account for that and + # not sleep for too long + elapsed = time.perf_counter() - last + if elapsed < self.interval: + thread_sleep(self.interval - elapsed) + + # after sleeping, make sure to take the current + # timestamp so we can use it next iteration + last = time.perf_counter() + + if self.buffer is not None: + self.buffer.flush() + + +class ThreadContinuousScheduler(ContinuousScheduler): + """ + This scheduler is based on running a daemon thread that will call + the sampler at a regular interval. + """ + + mode = "thread" # type: ContinuousProfilerMode + name = "sentry.profiler.ThreadContinuousScheduler" + + def __init__(self, frequency, options, sdk_info, capture_func): + # type: (int, Dict[str, Any], SDKInfo, Callable[[Envelope], None]) -> None + super().__init__(frequency, options, sdk_info, capture_func) + + self.thread = None # type: Optional[threading.Thread] + self.pid = None # type: Optional[int] + self.lock = threading.Lock() + + def should_autostart(self): + # type: () -> bool + return super().should_autostart() and self.pid != os.getpid() + + def ensure_running(self): + # type: () -> None + pid = os.getpid() + + # is running on the right process + if self.running and self.pid == pid: + return + + with self.lock: + # another thread may have tried to acquire the lock + # at the same time so it may start another thread + # make sure to check again before proceeding + if self.running and self.pid == pid: + return + + self.pid = pid + self.running = True + + # if the profiler thread is changing, + # we should create a new buffer along with it + self.reset_buffer() + + # make sure the thread is a daemon here otherwise this + # can keep the application running after other threads + # have exited + self.thread = threading.Thread(name=self.name, target=self.run, daemon=True) + + try: + self.thread.start() + except RuntimeError: + # Unfortunately at this point the interpreter is in a state that no + # longer allows us to spawn a thread and we have to bail. + self.running = False + self.thread = None + + def teardown(self): + # type: () -> None + if self.running: + self.running = False + + if self.thread is not None: + self.thread.join() + self.thread = None + + self.buffer = None + + +class GeventContinuousScheduler(ContinuousScheduler): + """ + This scheduler is based on the thread scheduler but adapted to work with + gevent. When using gevent, it may monkey patch the threading modules + (`threading` and `_thread`). This results in the use of greenlets instead + of native threads. + + This is an issue because the sampler CANNOT run in a greenlet because + 1. Other greenlets doing sync work will prevent the sampler from running + 2. The greenlet runs in the same thread as other greenlets so when taking + a sample, other greenlets will have been evicted from the thread. This + results in a sample containing only the sampler's code. + """ + + mode = "gevent" # type: ContinuousProfilerMode + + def __init__(self, frequency, options, sdk_info, capture_func): + # type: (int, Dict[str, Any], SDKInfo, Callable[[Envelope], None]) -> None + + if ThreadPool is None: + raise ValueError("Profiler mode: {} is not available".format(self.mode)) + + super().__init__(frequency, options, sdk_info, capture_func) + + self.thread = None # type: Optional[_ThreadPool] + self.pid = None # type: Optional[int] + self.lock = threading.Lock() + + def should_autostart(self): + # type: () -> bool + return super().should_autostart() and self.pid != os.getpid() + + def ensure_running(self): + # type: () -> None + pid = os.getpid() + + # is running on the right process + if self.running and self.pid == pid: + return + + with self.lock: + # another thread may have tried to acquire the lock + # at the same time so it may start another thread + # make sure to check again before proceeding + if self.running and self.pid == pid: + return + + self.pid = pid + self.running = True + + # if the profiler thread is changing, + # we should create a new buffer along with it + self.reset_buffer() + + self.thread = ThreadPool(1) # type: ignore[misc] + try: + self.thread.spawn(self.run) + except RuntimeError: + # Unfortunately at this point the interpreter is in a state that no + # longer allows us to spawn a thread and we have to bail. + self.running = False + self.thread = None + return + + def teardown(self): + # type: () -> None + if self.running: + self.running = False + + if self.thread is not None: + self.thread.join() + self.thread = None + + self.buffer = None + + +PROFILE_BUFFER_SECONDS = 10 + + +class ProfileBuffer: + def __init__(self, options, sdk_info, buffer_size, capture_func): + # type: (Dict[str, Any], SDKInfo, int, Callable[[Envelope], None]) -> None + self.options = options + self.sdk_info = sdk_info + self.buffer_size = buffer_size + self.capture_func = capture_func + + self.profiler_id = uuid.uuid4().hex + self.chunk = ProfileChunk() + + # Make sure to use the same clock to compute a sample's monotonic timestamp + # to ensure the timestamps are correctly aligned. + self.start_monotonic_time = now() + + # Make sure the start timestamp is defined only once per profiler id. + # This prevents issues with clock drift within a single profiler session. + # + # Subtracting the start_monotonic_time here to find a fixed starting position + # for relative monotonic timestamps for each sample. + self.start_timestamp = ( + datetime.now(timezone.utc).timestamp() - self.start_monotonic_time + ) + + def write(self, monotonic_time, sample): + # type: (float, ExtractedSample) -> None + if self.should_flush(monotonic_time): + self.flush() + self.chunk = ProfileChunk() + self.start_monotonic_time = now() + + self.chunk.write(self.start_timestamp + monotonic_time, sample) + + def should_flush(self, monotonic_time): + # type: (float) -> bool + + # If the delta between the new monotonic time and the start monotonic time + # exceeds the buffer size, it means we should flush the chunk + return monotonic_time - self.start_monotonic_time >= self.buffer_size + + def flush(self): + # type: () -> None + chunk = self.chunk.to_json(self.profiler_id, self.options, self.sdk_info) + envelope = Envelope() + envelope.add_profile_chunk(chunk) + self.capture_func(envelope) + + +class ProfileChunk: + def __init__(self): + # type: () -> None + self.chunk_id = uuid.uuid4().hex + + self.indexed_frames = {} # type: Dict[FrameId, int] + self.indexed_stacks = {} # type: Dict[StackId, int] + self.frames = [] # type: List[ProcessedFrame] + self.stacks = [] # type: List[ProcessedStack] + self.samples = [] # type: List[ProcessedSample] + + def write(self, ts, sample): + # type: (float, ExtractedSample) -> None + for tid, (stack_id, frame_ids, frames) in sample: + try: + # Check if the stack is indexed first, this lets us skip + # indexing frames if it's not necessary + if stack_id not in self.indexed_stacks: + for i, frame_id in enumerate(frame_ids): + if frame_id not in self.indexed_frames: + self.indexed_frames[frame_id] = len(self.indexed_frames) + self.frames.append(frames[i]) + + self.indexed_stacks[stack_id] = len(self.indexed_stacks) + self.stacks.append( + [self.indexed_frames[frame_id] for frame_id in frame_ids] + ) + + self.samples.append( + { + "timestamp": ts, + "thread_id": tid, + "stack_id": self.indexed_stacks[stack_id], + } + ) + except AttributeError: + # For some reason, the frame we get doesn't have certain attributes. + # When this happens, we abandon the current sample as it's bad. + capture_internal_exception(sys.exc_info()) + + def to_json(self, profiler_id, options, sdk_info): + # type: (str, Dict[str, Any], SDKInfo) -> Dict[str, Any] + profile = { + "frames": self.frames, + "stacks": self.stacks, + "samples": self.samples, + "thread_metadata": { + str(thread.ident): { + "name": str(thread.name), + } + for thread in threading.enumerate() + }, + } + + set_in_app_in_frames( + profile["frames"], + options["in_app_exclude"], + options["in_app_include"], + options["project_root"], + ) + + payload = { + "chunk_id": self.chunk_id, + "client_sdk": { + "name": sdk_info["name"], + "version": VERSION, + }, + "platform": "python", + "profile": profile, + "profiler_id": profiler_id, + "version": "2", + } + + for key in "release", "environment", "dist": + if options[key] is not None: + payload[key] = str(options[key]).strip() + + return payload diff --git a/aws/lambda_demo/sentry_sdk/profiler/transaction_profiler.py b/aws/lambda_demo/sentry_sdk/profiler/transaction_profiler.py new file mode 100644 index 000000000..f579c441f --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/profiler/transaction_profiler.py @@ -0,0 +1,837 @@ +""" +This file is originally based on code from https://github.com/nylas/nylas-perftools, +which is published under the following license: + +The MIT License (MIT) + +Copyright (c) 2014 Nylas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +import atexit +import os +import platform +import random +import sys +import threading +import time +import uuid +import warnings +from abc import ABC, abstractmethod +from collections import deque + +import sentry_sdk +from sentry_sdk._lru_cache import LRUCache +from sentry_sdk.profiler.utils import ( + DEFAULT_SAMPLING_FREQUENCY, + extract_stack, +) +from sentry_sdk.utils import ( + capture_internal_exception, + get_current_thread_meta, + is_gevent, + is_valid_sample_rate, + logger, + nanosecond_time, + set_in_app_in_frames, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import Deque + from typing import Dict + from typing import List + from typing import Optional + from typing import Set + from typing import Type + from typing_extensions import TypedDict + + from sentry_sdk.profiler.utils import ( + ProcessedStack, + ProcessedFrame, + ProcessedThreadMetadata, + FrameId, + StackId, + ThreadId, + ExtractedSample, + ) + from sentry_sdk._types import Event, SamplingContext, ProfilerMode + + ProcessedSample = TypedDict( + "ProcessedSample", + { + "elapsed_since_start_ns": str, + "thread_id": ThreadId, + "stack_id": int, + }, + ) + + ProcessedProfile = TypedDict( + "ProcessedProfile", + { + "frames": List[ProcessedFrame], + "stacks": List[ProcessedStack], + "samples": List[ProcessedSample], + "thread_metadata": Dict[ThreadId, ProcessedThreadMetadata], + }, + ) + + +try: + from gevent.monkey import get_original + from gevent.threadpool import ThreadPool as _ThreadPool + + ThreadPool = _ThreadPool # type: Optional[Type[_ThreadPool]] + thread_sleep = get_original("time", "sleep") +except ImportError: + thread_sleep = time.sleep + + ThreadPool = None + + +_scheduler = None # type: Optional[Scheduler] + + +# The minimum number of unique samples that must exist in a profile to be +# considered valid. +PROFILE_MINIMUM_SAMPLES = 2 + + +def has_profiling_enabled(options): + # type: (Dict[str, Any]) -> bool + profiles_sampler = options["profiles_sampler"] + if profiles_sampler is not None: + return True + + profiles_sample_rate = options["profiles_sample_rate"] + if profiles_sample_rate is not None and profiles_sample_rate > 0: + return True + + profiles_sample_rate = options["_experiments"].get("profiles_sample_rate") + if profiles_sample_rate is not None: + logger.warning( + "_experiments['profiles_sample_rate'] is deprecated. " + "Please use the non-experimental profiles_sample_rate option " + "directly." + ) + if profiles_sample_rate > 0: + return True + + return False + + +def setup_profiler(options): + # type: (Dict[str, Any]) -> bool + global _scheduler + + if _scheduler is not None: + logger.debug("[Profiling] Profiler is already setup") + return False + + frequency = DEFAULT_SAMPLING_FREQUENCY + + if is_gevent(): + # If gevent has patched the threading modules then we cannot rely on + # them to spawn a native thread for sampling. + # Instead we default to the GeventScheduler which is capable of + # spawning native threads within gevent. + default_profiler_mode = GeventScheduler.mode + else: + default_profiler_mode = ThreadScheduler.mode + + if options.get("profiler_mode") is not None: + profiler_mode = options["profiler_mode"] + else: + profiler_mode = options.get("_experiments", {}).get("profiler_mode") + if profiler_mode is not None: + logger.warning( + "_experiments['profiler_mode'] is deprecated. Please use the " + "non-experimental profiler_mode option directly." + ) + profiler_mode = profiler_mode or default_profiler_mode + + if ( + profiler_mode == ThreadScheduler.mode + # for legacy reasons, we'll keep supporting sleep mode for this scheduler + or profiler_mode == "sleep" + ): + _scheduler = ThreadScheduler(frequency=frequency) + elif profiler_mode == GeventScheduler.mode: + _scheduler = GeventScheduler(frequency=frequency) + else: + raise ValueError("Unknown profiler mode: {}".format(profiler_mode)) + + logger.debug( + "[Profiling] Setting up profiler in {mode} mode".format(mode=_scheduler.mode) + ) + _scheduler.setup() + + atexit.register(teardown_profiler) + + return True + + +def teardown_profiler(): + # type: () -> None + + global _scheduler + + if _scheduler is not None: + _scheduler.teardown() + + _scheduler = None + + +MAX_PROFILE_DURATION_NS = int(3e10) # 30 seconds + + +class Profile: + def __init__( + self, + sampled, # type: Optional[bool] + start_ns, # type: int + hub=None, # type: Optional[sentry_sdk.Hub] + scheduler=None, # type: Optional[Scheduler] + ): + # type: (...) -> None + self.scheduler = _scheduler if scheduler is None else scheduler + + self.event_id = uuid.uuid4().hex # type: str + + self.sampled = sampled # type: Optional[bool] + + # Various framework integrations are capable of overwriting the active thread id. + # If it is set to `None` at the end of the profile, we fall back to the default. + self._default_active_thread_id = get_current_thread_meta()[0] or 0 # type: int + self.active_thread_id = None # type: Optional[int] + + try: + self.start_ns = start_ns # type: int + except AttributeError: + self.start_ns = 0 + + self.stop_ns = 0 # type: int + self.active = False # type: bool + + self.indexed_frames = {} # type: Dict[FrameId, int] + self.indexed_stacks = {} # type: Dict[StackId, int] + self.frames = [] # type: List[ProcessedFrame] + self.stacks = [] # type: List[ProcessedStack] + self.samples = [] # type: List[ProcessedSample] + + self.unique_samples = 0 + + # Backwards compatibility with the old hub property + self._hub = None # type: Optional[sentry_sdk.Hub] + if hub is not None: + self._hub = hub + warnings.warn( + "The `hub` parameter is deprecated. Please do not use it.", + DeprecationWarning, + stacklevel=2, + ) + + def update_active_thread_id(self): + # type: () -> None + self.active_thread_id = get_current_thread_meta()[0] + logger.debug( + "[Profiling] updating active thread id to {tid}".format( + tid=self.active_thread_id + ) + ) + + def _set_initial_sampling_decision(self, sampling_context): + # type: (SamplingContext) -> None + """ + Sets the profile's sampling decision according to the following + precedence rules: + + 1. If the transaction to be profiled is not sampled, that decision + will be used, regardless of anything else. + + 2. Use `profiles_sample_rate` to decide. + """ + + # The corresponding transaction was not sampled, + # so don't generate a profile for it. + if not self.sampled: + logger.debug( + "[Profiling] Discarding profile because transaction is discarded." + ) + self.sampled = False + return + + # The profiler hasn't been properly initialized. + if self.scheduler is None: + logger.debug( + "[Profiling] Discarding profile because profiler was not started." + ) + self.sampled = False + return + + client = sentry_sdk.get_client() + if not client.is_active(): + self.sampled = False + return + + options = client.options + + if callable(options.get("profiles_sampler")): + sample_rate = options["profiles_sampler"](sampling_context) + elif options["profiles_sample_rate"] is not None: + sample_rate = options["profiles_sample_rate"] + else: + sample_rate = options["_experiments"].get("profiles_sample_rate") + + # The profiles_sample_rate option was not set, so profiling + # was never enabled. + if sample_rate is None: + logger.debug( + "[Profiling] Discarding profile because profiling was not enabled." + ) + self.sampled = False + return + + if not is_valid_sample_rate(sample_rate, source="Profiling"): + logger.warning( + "[Profiling] Discarding profile because of invalid sample rate." + ) + self.sampled = False + return + + # Now we roll the dice. random.random is inclusive of 0, but not of 1, + # so strict < is safe here. In case sample_rate is a boolean, cast it + # to a float (True becomes 1.0 and False becomes 0.0) + self.sampled = random.random() < float(sample_rate) + + if self.sampled: + logger.debug("[Profiling] Initializing profile") + else: + logger.debug( + "[Profiling] Discarding profile because it's not included in the random sample (sample rate = {sample_rate})".format( + sample_rate=float(sample_rate) + ) + ) + + def start(self): + # type: () -> None + if not self.sampled or self.active: + return + + assert self.scheduler, "No scheduler specified" + logger.debug("[Profiling] Starting profile") + self.active = True + if not self.start_ns: + self.start_ns = nanosecond_time() + self.scheduler.start_profiling(self) + + def stop(self): + # type: () -> None + if not self.sampled or not self.active: + return + + assert self.scheduler, "No scheduler specified" + logger.debug("[Profiling] Stopping profile") + self.active = False + self.stop_ns = nanosecond_time() + + def __enter__(self): + # type: () -> Profile + scope = sentry_sdk.get_isolation_scope() + old_profile = scope.profile + scope.profile = self + + self._context_manager_state = (scope, old_profile) + + self.start() + + return self + + def __exit__(self, ty, value, tb): + # type: (Optional[Any], Optional[Any], Optional[Any]) -> None + self.stop() + + scope, old_profile = self._context_manager_state + del self._context_manager_state + + scope.profile = old_profile + + def write(self, ts, sample): + # type: (int, ExtractedSample) -> None + if not self.active: + return + + if ts < self.start_ns: + return + + offset = ts - self.start_ns + if offset > MAX_PROFILE_DURATION_NS: + self.stop() + return + + self.unique_samples += 1 + + elapsed_since_start_ns = str(offset) + + for tid, (stack_id, frame_ids, frames) in sample: + try: + # Check if the stack is indexed first, this lets us skip + # indexing frames if it's not necessary + if stack_id not in self.indexed_stacks: + for i, frame_id in enumerate(frame_ids): + if frame_id not in self.indexed_frames: + self.indexed_frames[frame_id] = len(self.indexed_frames) + self.frames.append(frames[i]) + + self.indexed_stacks[stack_id] = len(self.indexed_stacks) + self.stacks.append( + [self.indexed_frames[frame_id] for frame_id in frame_ids] + ) + + self.samples.append( + { + "elapsed_since_start_ns": elapsed_since_start_ns, + "thread_id": tid, + "stack_id": self.indexed_stacks[stack_id], + } + ) + except AttributeError: + # For some reason, the frame we get doesn't have certain attributes. + # When this happens, we abandon the current sample as it's bad. + capture_internal_exception(sys.exc_info()) + + def process(self): + # type: () -> ProcessedProfile + + # This collects the thread metadata at the end of a profile. Doing it + # this way means that any threads that terminate before the profile ends + # will not have any metadata associated with it. + thread_metadata = { + str(thread.ident): { + "name": str(thread.name), + } + for thread in threading.enumerate() + } # type: Dict[str, ProcessedThreadMetadata] + + return { + "frames": self.frames, + "stacks": self.stacks, + "samples": self.samples, + "thread_metadata": thread_metadata, + } + + def to_json(self, event_opt, options): + # type: (Event, Dict[str, Any]) -> Dict[str, Any] + profile = self.process() + + set_in_app_in_frames( + profile["frames"], + options["in_app_exclude"], + options["in_app_include"], + options["project_root"], + ) + + return { + "environment": event_opt.get("environment"), + "event_id": self.event_id, + "platform": "python", + "profile": profile, + "release": event_opt.get("release", ""), + "timestamp": event_opt["start_timestamp"], + "version": "1", + "device": { + "architecture": platform.machine(), + }, + "os": { + "name": platform.system(), + "version": platform.release(), + }, + "runtime": { + "name": platform.python_implementation(), + "version": platform.python_version(), + }, + "transactions": [ + { + "id": event_opt["event_id"], + "name": event_opt["transaction"], + # we start the transaction before the profile and this is + # the transaction start time relative to the profile, so we + # hardcode it to 0 until we can start the profile before + "relative_start_ns": "0", + # use the duration of the profile instead of the transaction + # because we end the transaction after the profile + "relative_end_ns": str(self.stop_ns - self.start_ns), + "trace_id": event_opt["contexts"]["trace"]["trace_id"], + "active_thread_id": str( + self._default_active_thread_id + if self.active_thread_id is None + else self.active_thread_id + ), + } + ], + } + + def valid(self): + # type: () -> bool + client = sentry_sdk.get_client() + if not client.is_active(): + return False + + if not has_profiling_enabled(client.options): + return False + + if self.sampled is None or not self.sampled: + if client.transport: + client.transport.record_lost_event( + "sample_rate", data_category="profile" + ) + return False + + if self.unique_samples < PROFILE_MINIMUM_SAMPLES: + if client.transport: + client.transport.record_lost_event( + "insufficient_data", data_category="profile" + ) + logger.debug("[Profiling] Discarding profile because insufficient samples.") + return False + + return True + + @property + def hub(self): + # type: () -> Optional[sentry_sdk.Hub] + warnings.warn( + "The `hub` attribute is deprecated. Please do not access it.", + DeprecationWarning, + stacklevel=2, + ) + return self._hub + + @hub.setter + def hub(self, value): + # type: (Optional[sentry_sdk.Hub]) -> None + warnings.warn( + "The `hub` attribute is deprecated. Please do not set it.", + DeprecationWarning, + stacklevel=2, + ) + self._hub = value + + +class Scheduler(ABC): + mode = "unknown" # type: ProfilerMode + + def __init__(self, frequency): + # type: (int) -> None + self.interval = 1.0 / frequency + + self.sampler = self.make_sampler() + + # cap the number of new profiles at any time so it does not grow infinitely + self.new_profiles = deque(maxlen=128) # type: Deque[Profile] + self.active_profiles = set() # type: Set[Profile] + + def __enter__(self): + # type: () -> Scheduler + self.setup() + return self + + def __exit__(self, ty, value, tb): + # type: (Optional[Any], Optional[Any], Optional[Any]) -> None + self.teardown() + + @abstractmethod + def setup(self): + # type: () -> None + pass + + @abstractmethod + def teardown(self): + # type: () -> None + pass + + def ensure_running(self): + # type: () -> None + """ + Ensure the scheduler is running. By default, this method is a no-op. + The method should be overridden by any implementation for which it is + relevant. + """ + return None + + def start_profiling(self, profile): + # type: (Profile) -> None + self.ensure_running() + self.new_profiles.append(profile) + + def make_sampler(self): + # type: () -> Callable[..., None] + cwd = os.getcwd() + + cache = LRUCache(max_size=256) + + def _sample_stack(*args, **kwargs): + # type: (*Any, **Any) -> None + """ + Take a sample of the stack on all the threads in the process. + This should be called at a regular interval to collect samples. + """ + # no profiles taking place, so we can stop early + if not self.new_profiles and not self.active_profiles: + # make sure to clear the cache if we're not profiling so we dont + # keep a reference to the last stack of frames around + return + + # This is the number of profiles we want to pop off. + # It's possible another thread adds a new profile to + # the list and we spend longer than we want inside + # the loop below. + # + # Also make sure to set this value before extracting + # frames so we do not write to any new profiles that + # were started after this point. + new_profiles = len(self.new_profiles) + + now = nanosecond_time() + + try: + sample = [ + (str(tid), extract_stack(frame, cache, cwd)) + for tid, frame in sys._current_frames().items() + ] + except AttributeError: + # For some reason, the frame we get doesn't have certain attributes. + # When this happens, we abandon the current sample as it's bad. + capture_internal_exception(sys.exc_info()) + return + + # Move the new profiles into the active_profiles set. + # + # We cannot directly add the to active_profiles set + # in `start_profiling` because it is called from other + # threads which can cause a RuntimeError when it the + # set sizes changes during iteration without a lock. + # + # We also want to avoid using a lock here so threads + # that are starting profiles are not blocked until it + # can acquire the lock. + for _ in range(new_profiles): + self.active_profiles.add(self.new_profiles.popleft()) + + inactive_profiles = [] + + for profile in self.active_profiles: + if profile.active: + profile.write(now, sample) + else: + # If a thread is marked inactive, we buffer it + # to `inactive_profiles` so it can be removed. + # We cannot remove it here as it would result + # in a RuntimeError. + inactive_profiles.append(profile) + + for profile in inactive_profiles: + self.active_profiles.remove(profile) + + return _sample_stack + + +class ThreadScheduler(Scheduler): + """ + This scheduler is based on running a daemon thread that will call + the sampler at a regular interval. + """ + + mode = "thread" # type: ProfilerMode + name = "sentry.profiler.ThreadScheduler" + + def __init__(self, frequency): + # type: (int) -> None + super().__init__(frequency=frequency) + + # used to signal to the thread that it should stop + self.running = False + self.thread = None # type: Optional[threading.Thread] + self.pid = None # type: Optional[int] + self.lock = threading.Lock() + + def setup(self): + # type: () -> None + pass + + def teardown(self): + # type: () -> None + if self.running: + self.running = False + if self.thread is not None: + self.thread.join() + + def ensure_running(self): + # type: () -> None + """ + Check that the profiler has an active thread to run in, and start one if + that's not the case. + + Note that this might fail (e.g. in Python 3.12 it's not possible to + spawn new threads at interpreter shutdown). In that case self.running + will be False after running this function. + """ + pid = os.getpid() + + # is running on the right process + if self.running and self.pid == pid: + return + + with self.lock: + # another thread may have tried to acquire the lock + # at the same time so it may start another thread + # make sure to check again before proceeding + if self.running and self.pid == pid: + return + + self.pid = pid + self.running = True + + # make sure the thread is a daemon here otherwise this + # can keep the application running after other threads + # have exited + self.thread = threading.Thread(name=self.name, target=self.run, daemon=True) + try: + self.thread.start() + except RuntimeError: + # Unfortunately at this point the interpreter is in a state that no + # longer allows us to spawn a thread and we have to bail. + self.running = False + self.thread = None + return + + def run(self): + # type: () -> None + last = time.perf_counter() + + while self.running: + self.sampler() + + # some time may have elapsed since the last time + # we sampled, so we need to account for that and + # not sleep for too long + elapsed = time.perf_counter() - last + if elapsed < self.interval: + thread_sleep(self.interval - elapsed) + + # after sleeping, make sure to take the current + # timestamp so we can use it next iteration + last = time.perf_counter() + + +class GeventScheduler(Scheduler): + """ + This scheduler is based on the thread scheduler but adapted to work with + gevent. When using gevent, it may monkey patch the threading modules + (`threading` and `_thread`). This results in the use of greenlets instead + of native threads. + + This is an issue because the sampler CANNOT run in a greenlet because + 1. Other greenlets doing sync work will prevent the sampler from running + 2. The greenlet runs in the same thread as other greenlets so when taking + a sample, other greenlets will have been evicted from the thread. This + results in a sample containing only the sampler's code. + """ + + mode = "gevent" # type: ProfilerMode + name = "sentry.profiler.GeventScheduler" + + def __init__(self, frequency): + # type: (int) -> None + + if ThreadPool is None: + raise ValueError("Profiler mode: {} is not available".format(self.mode)) + + super().__init__(frequency=frequency) + + # used to signal to the thread that it should stop + self.running = False + self.thread = None # type: Optional[_ThreadPool] + self.pid = None # type: Optional[int] + + # This intentionally uses the gevent patched threading.Lock. + # The lock will be required when first trying to start profiles + # as we need to spawn the profiler thread from the greenlets. + self.lock = threading.Lock() + + def setup(self): + # type: () -> None + pass + + def teardown(self): + # type: () -> None + if self.running: + self.running = False + if self.thread is not None: + self.thread.join() + + def ensure_running(self): + # type: () -> None + pid = os.getpid() + + # is running on the right process + if self.running and self.pid == pid: + return + + with self.lock: + # another thread may have tried to acquire the lock + # at the same time so it may start another thread + # make sure to check again before proceeding + if self.running and self.pid == pid: + return + + self.pid = pid + self.running = True + + self.thread = ThreadPool(1) # type: ignore[misc] + try: + self.thread.spawn(self.run) + except RuntimeError: + # Unfortunately at this point the interpreter is in a state that no + # longer allows us to spawn a thread and we have to bail. + self.running = False + self.thread = None + return + + def run(self): + # type: () -> None + last = time.perf_counter() + + while self.running: + self.sampler() + + # some time may have elapsed since the last time + # we sampled, so we need to account for that and + # not sleep for too long + elapsed = time.perf_counter() - last + if elapsed < self.interval: + thread_sleep(self.interval - elapsed) + + # after sleeping, make sure to take the current + # timestamp so we can use it next iteration + last = time.perf_counter() diff --git a/aws/lambda_demo/sentry_sdk/profiler/utils.py b/aws/lambda_demo/sentry_sdk/profiler/utils.py new file mode 100644 index 000000000..3554cddb5 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/profiler/utils.py @@ -0,0 +1,199 @@ +import os +from collections import deque + +from sentry_sdk._compat import PY311 +from sentry_sdk.utils import filename_for_module + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from sentry_sdk._lru_cache import LRUCache + from types import FrameType + from typing import Deque + from typing import List + from typing import Optional + from typing import Sequence + from typing import Tuple + from typing_extensions import TypedDict + + ThreadId = str + + ProcessedStack = List[int] + + ProcessedFrame = TypedDict( + "ProcessedFrame", + { + "abs_path": str, + "filename": Optional[str], + "function": str, + "lineno": int, + "module": Optional[str], + }, + ) + + ProcessedThreadMetadata = TypedDict( + "ProcessedThreadMetadata", + {"name": str}, + ) + + FrameId = Tuple[ + str, # abs_path + int, # lineno + str, # function + ] + FrameIds = Tuple[FrameId, ...] + + # The exact value of this id is not very meaningful. The purpose + # of this id is to give us a compact and unique identifier for a + # raw stack that can be used as a key to a dictionary so that it + # can be used during the sampled format generation. + StackId = Tuple[int, int] + + ExtractedStack = Tuple[StackId, FrameIds, List[ProcessedFrame]] + ExtractedSample = Sequence[Tuple[ThreadId, ExtractedStack]] + +# The default sampling frequency to use. This is set at 101 in order to +# mitigate the effects of lockstep sampling. +DEFAULT_SAMPLING_FREQUENCY = 101 + + +# We want to impose a stack depth limit so that samples aren't too large. +MAX_STACK_DEPTH = 128 + + +if PY311: + + def get_frame_name(frame): + # type: (FrameType) -> str + return frame.f_code.co_qualname + +else: + + def get_frame_name(frame): + # type: (FrameType) -> str + + f_code = frame.f_code + co_varnames = f_code.co_varnames + + # co_name only contains the frame name. If the frame was a method, + # the class name will NOT be included. + name = f_code.co_name + + # if it was a method, we can get the class name by inspecting + # the f_locals for the `self` argument + try: + if ( + # the co_varnames start with the frame's positional arguments + # and we expect the first to be `self` if its an instance method + co_varnames + and co_varnames[0] == "self" + and "self" in frame.f_locals + ): + for cls in type(frame.f_locals["self"]).__mro__: + if name in cls.__dict__: + return "{}.{}".format(cls.__name__, name) + except (AttributeError, ValueError): + pass + + # if it was a class method, (decorated with `@classmethod`) + # we can get the class name by inspecting the f_locals for the `cls` argument + try: + if ( + # the co_varnames start with the frame's positional arguments + # and we expect the first to be `cls` if its a class method + co_varnames + and co_varnames[0] == "cls" + and "cls" in frame.f_locals + ): + for cls in frame.f_locals["cls"].__mro__: + if name in cls.__dict__: + return "{}.{}".format(cls.__name__, name) + except (AttributeError, ValueError): + pass + + # nothing we can do if it is a staticmethod (decorated with @staticmethod) + + # we've done all we can, time to give up and return what we have + return name + + +def frame_id(raw_frame): + # type: (FrameType) -> FrameId + return (raw_frame.f_code.co_filename, raw_frame.f_lineno, get_frame_name(raw_frame)) + + +def extract_frame(fid, raw_frame, cwd): + # type: (FrameId, FrameType, str) -> ProcessedFrame + abs_path = raw_frame.f_code.co_filename + + try: + module = raw_frame.f_globals["__name__"] + except Exception: + module = None + + # namedtuples can be many times slower when initialing + # and accessing attribute so we opt to use a tuple here instead + return { + # This originally was `os.path.abspath(abs_path)` but that had + # a large performance overhead. + # + # According to docs, this is equivalent to + # `os.path.normpath(os.path.join(os.getcwd(), path))`. + # The `os.getcwd()` call is slow here, so we precompute it. + # + # Additionally, since we are using normalized path already, + # we skip calling `os.path.normpath` entirely. + "abs_path": os.path.join(cwd, abs_path), + "module": module, + "filename": filename_for_module(module, abs_path) or None, + "function": fid[2], + "lineno": raw_frame.f_lineno, + } + + +def extract_stack( + raw_frame, # type: Optional[FrameType] + cache, # type: LRUCache + cwd, # type: str + max_stack_depth=MAX_STACK_DEPTH, # type: int +): + # type: (...) -> ExtractedStack + """ + Extracts the stack starting the specified frame. The extracted stack + assumes the specified frame is the top of the stack, and works back + to the bottom of the stack. + + In the event that the stack is more than `MAX_STACK_DEPTH` frames deep, + only the first `MAX_STACK_DEPTH` frames will be returned. + """ + + raw_frames = deque(maxlen=max_stack_depth) # type: Deque[FrameType] + + while raw_frame is not None: + f_back = raw_frame.f_back + raw_frames.append(raw_frame) + raw_frame = f_back + + frame_ids = tuple(frame_id(raw_frame) for raw_frame in raw_frames) + frames = [] + for i, fid in enumerate(frame_ids): + frame = cache.get(fid) + if frame is None: + frame = extract_frame(fid, raw_frames[i], cwd) + cache.set(fid, frame) + frames.append(frame) + + # Instead of mapping the stack into frame ids and hashing + # that as a tuple, we can directly hash the stack. + # This saves us from having to generate yet another list. + # Additionally, using the stack as the key directly is + # costly because the stack can be large, so we pre-hash + # the stack, and use the hash as the key as this will be + # needed a few times to improve performance. + # + # To Reduce the likelihood of hash collisions, we include + # the stack depth. This means that only stacks of the same + # depth can suffer from hash collisions. + stack_id = len(raw_frames), hash(frame_ids) + + return stack_id, frame_ids, frames diff --git a/aws/lambda_demo/sentry_sdk/py.typed b/aws/lambda_demo/sentry_sdk/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/aws/lambda_demo/sentry_sdk/scope.py b/aws/lambda_demo/sentry_sdk/scope.py new file mode 100644 index 000000000..ab0f1f415 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/scope.py @@ -0,0 +1,1744 @@ +import os +import sys +import warnings +from copy import copy, deepcopy +from collections import deque +from contextlib import contextmanager +from enum import Enum +from datetime import datetime, timezone +from functools import wraps +from itertools import chain + +from sentry_sdk.attachments import Attachment +from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS, FALSE_VALUES, INSTRUMENTER +from sentry_sdk.feature_flags import FlagBuffer, DEFAULT_FLAG_CAPACITY +from sentry_sdk.profiler.continuous_profiler import try_autostart_continuous_profiler +from sentry_sdk.profiler.transaction_profiler import Profile +from sentry_sdk.session import Session +from sentry_sdk.tracing_utils import ( + Baggage, + has_tracing_enabled, + normalize_incoming_data, + PropagationContext, +) +from sentry_sdk.tracing import ( + BAGGAGE_HEADER_NAME, + SENTRY_TRACE_HEADER_NAME, + NoOpSpan, + Span, + Transaction, +) +from sentry_sdk.utils import ( + capture_internal_exception, + capture_internal_exceptions, + ContextVar, + datetime_from_isoformat, + disable_capture_event, + event_from_exception, + exc_info_from_error, + logger, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Mapping, MutableMapping + + from typing import Any + from typing import Callable + from typing import Deque + from typing import Dict + from typing import Generator + from typing import Iterator + from typing import List + from typing import Optional + from typing import ParamSpec + from typing import Tuple + from typing import TypeVar + from typing import Union + + from typing_extensions import Unpack + + from sentry_sdk._types import ( + Breadcrumb, + BreadcrumbHint, + ErrorProcessor, + Event, + EventProcessor, + ExcInfo, + Hint, + LogLevelStr, + SamplingContext, + Type, + ) + + from sentry_sdk.tracing import TransactionKwargs + + import sentry_sdk + + P = ParamSpec("P") + R = TypeVar("R") + + F = TypeVar("F", bound=Callable[..., Any]) + T = TypeVar("T") + + +# Holds data that will be added to **all** events sent by this process. +# In case this is a http server (think web framework) with multiple users +# the data will be added to events of all users. +# Typically this is used for process wide data such as the release. +_global_scope = None # type: Optional[Scope] + +# Holds data for the active request. +# This is used to isolate data for different requests or users. +# The isolation scope is usually created by integrations, but may also +# be created manually +_isolation_scope = ContextVar("isolation_scope", default=None) + +# Holds data for the active span. +# This can be used to manually add additional data to a span. +_current_scope = ContextVar("current_scope", default=None) + +global_event_processors = [] # type: List[EventProcessor] + + +class ScopeType(Enum): + CURRENT = "current" + ISOLATION = "isolation" + GLOBAL = "global" + MERGED = "merged" + + +class _ScopeManager: + def __init__(self, hub=None): + # type: (Optional[Any]) -> None + self._old_scopes = [] # type: List[Scope] + + def __enter__(self): + # type: () -> Scope + isolation_scope = Scope.get_isolation_scope() + + self._old_scopes.append(isolation_scope) + + forked_scope = isolation_scope.fork() + _isolation_scope.set(forked_scope) + + return forked_scope + + def __exit__(self, exc_type, exc_value, tb): + # type: (Any, Any, Any) -> None + old_scope = self._old_scopes.pop() + _isolation_scope.set(old_scope) + + +def add_global_event_processor(processor): + # type: (EventProcessor) -> None + global_event_processors.append(processor) + + +def _attr_setter(fn): + # type: (Any) -> Any + return property(fset=fn, doc=fn.__doc__) + + +def _disable_capture(fn): + # type: (F) -> F + @wraps(fn) + def wrapper(self, *args, **kwargs): + # type: (Any, *Dict[str, Any], **Any) -> Any + if not self._should_capture: + return + try: + self._should_capture = False + return fn(self, *args, **kwargs) + finally: + self._should_capture = True + + return wrapper # type: ignore + + +class Scope: + """The scope holds extra information that should be sent with all + events that belong to it. + """ + + # NOTE: Even though it should not happen, the scope needs to not crash when + # accessed by multiple threads. It's fine if it's full of races, but those + # races should never make the user application crash. + # + # The same needs to hold for any accesses of the scope the SDK makes. + + __slots__ = ( + "_level", + "_name", + "_fingerprint", + # note that for legacy reasons, _transaction is the transaction *name*, + # not a Transaction object (the object is stored in _span) + "_transaction", + "_transaction_info", + "_user", + "_tags", + "_contexts", + "_extras", + "_breadcrumbs", + "_event_processors", + "_error_processors", + "_should_capture", + "_span", + "_session", + "_attachments", + "_force_auto_session_tracking", + "_profile", + "_propagation_context", + "client", + "_type", + "_last_event_id", + "_flags", + ) + + def __init__(self, ty=None, client=None): + # type: (Optional[ScopeType], Optional[sentry_sdk.Client]) -> None + self._type = ty + + self._event_processors = [] # type: List[EventProcessor] + self._error_processors = [] # type: List[ErrorProcessor] + + self._name = None # type: Optional[str] + self._propagation_context = None # type: Optional[PropagationContext] + + self.client = NonRecordingClient() # type: sentry_sdk.client.BaseClient + + if client is not None: + self.set_client(client) + + self.clear() + + incoming_trace_information = self._load_trace_data_from_env() + self.generate_propagation_context(incoming_data=incoming_trace_information) + + def __copy__(self): + # type: () -> Scope + """ + Returns a copy of this scope. + This also creates a copy of all referenced data structures. + """ + rv = object.__new__(self.__class__) # type: Scope + + rv._type = self._type + rv.client = self.client + rv._level = self._level + rv._name = self._name + rv._fingerprint = self._fingerprint + rv._transaction = self._transaction + rv._transaction_info = dict(self._transaction_info) + rv._user = self._user + + rv._tags = dict(self._tags) + rv._contexts = dict(self._contexts) + rv._extras = dict(self._extras) + + rv._breadcrumbs = copy(self._breadcrumbs) + rv._event_processors = list(self._event_processors) + rv._error_processors = list(self._error_processors) + rv._propagation_context = self._propagation_context + + rv._should_capture = self._should_capture + rv._span = self._span + rv._session = self._session + rv._force_auto_session_tracking = self._force_auto_session_tracking + rv._attachments = list(self._attachments) + + rv._profile = self._profile + + rv._last_event_id = self._last_event_id + + rv._flags = deepcopy(self._flags) + + return rv + + @classmethod + def get_current_scope(cls): + # type: () -> Scope + """ + .. versionadded:: 2.0.0 + + Returns the current scope. + """ + current_scope = _current_scope.get() + if current_scope is None: + current_scope = Scope(ty=ScopeType.CURRENT) + _current_scope.set(current_scope) + + return current_scope + + @classmethod + def set_current_scope(cls, new_current_scope): + # type: (Scope) -> None + """ + .. versionadded:: 2.0.0 + + Sets the given scope as the new current scope overwriting the existing current scope. + :param new_current_scope: The scope to set as the new current scope. + """ + _current_scope.set(new_current_scope) + + @classmethod + def get_isolation_scope(cls): + # type: () -> Scope + """ + .. versionadded:: 2.0.0 + + Returns the isolation scope. + """ + isolation_scope = _isolation_scope.get() + if isolation_scope is None: + isolation_scope = Scope(ty=ScopeType.ISOLATION) + _isolation_scope.set(isolation_scope) + + return isolation_scope + + @classmethod + def set_isolation_scope(cls, new_isolation_scope): + # type: (Scope) -> None + """ + .. versionadded:: 2.0.0 + + Sets the given scope as the new isolation scope overwriting the existing isolation scope. + :param new_isolation_scope: The scope to set as the new isolation scope. + """ + _isolation_scope.set(new_isolation_scope) + + @classmethod + def get_global_scope(cls): + # type: () -> Scope + """ + .. versionadded:: 2.0.0 + + Returns the global scope. + """ + global _global_scope + if _global_scope is None: + _global_scope = Scope(ty=ScopeType.GLOBAL) + + return _global_scope + + @classmethod + def last_event_id(cls): + # type: () -> Optional[str] + """ + .. versionadded:: 2.2.0 + + Returns event ID of the event most recently captured by the isolation scope, or None if no event + has been captured. We do not consider events that are dropped, e.g. by a before_send hook. + Transactions also are not considered events in this context. + + The event corresponding to the returned event ID is NOT guaranteed to actually be sent to Sentry; + whether the event is sent depends on the transport. The event could be sent later or not at all. + Even a sent event could fail to arrive in Sentry due to network issues, exhausted quotas, or + various other reasons. + """ + return cls.get_isolation_scope()._last_event_id + + def _merge_scopes(self, additional_scope=None, additional_scope_kwargs=None): + # type: (Optional[Scope], Optional[Dict[str, Any]]) -> Scope + """ + Merges global, isolation and current scope into a new scope and + adds the given additional scope or additional scope kwargs to it. + """ + if additional_scope and additional_scope_kwargs: + raise TypeError("cannot provide scope and kwargs") + + final_scope = copy(_global_scope) if _global_scope is not None else Scope() + final_scope._type = ScopeType.MERGED + + isolation_scope = _isolation_scope.get() + if isolation_scope is not None: + final_scope.update_from_scope(isolation_scope) + + current_scope = _current_scope.get() + if current_scope is not None: + final_scope.update_from_scope(current_scope) + + if self != current_scope and self != isolation_scope: + final_scope.update_from_scope(self) + + if additional_scope is not None: + if callable(additional_scope): + additional_scope(final_scope) + else: + final_scope.update_from_scope(additional_scope) + + elif additional_scope_kwargs: + final_scope.update_from_kwargs(**additional_scope_kwargs) + + return final_scope + + @classmethod + def get_client(cls): + # type: () -> sentry_sdk.client.BaseClient + """ + .. versionadded:: 2.0.0 + + Returns the currently used :py:class:`sentry_sdk.Client`. + This checks the current scope, the isolation scope and the global scope for a client. + If no client is available a :py:class:`sentry_sdk.client.NonRecordingClient` is returned. + """ + current_scope = _current_scope.get() + try: + client = current_scope.client + except AttributeError: + client = None + + if client is not None and client.is_active(): + return client + + isolation_scope = _isolation_scope.get() + try: + client = isolation_scope.client + except AttributeError: + client = None + + if client is not None and client.is_active(): + return client + + try: + client = _global_scope.client # type: ignore + except AttributeError: + client = None + + if client is not None and client.is_active(): + return client + + return NonRecordingClient() + + def set_client(self, client=None): + # type: (Optional[sentry_sdk.client.BaseClient]) -> None + """ + .. versionadded:: 2.0.0 + + Sets the client for this scope. + + :param client: The client to use in this scope. + If `None` the client of the scope will be replaced by a :py:class:`sentry_sdk.NonRecordingClient`. + + """ + self.client = client if client is not None else NonRecordingClient() + + def fork(self): + # type: () -> Scope + """ + .. versionadded:: 2.0.0 + + Returns a fork of this scope. + """ + forked_scope = copy(self) + return forked_scope + + def _load_trace_data_from_env(self): + # type: () -> Optional[Dict[str, str]] + """ + Load Sentry trace id and baggage from environment variables. + Can be disabled by setting SENTRY_USE_ENVIRONMENT to "false". + """ + incoming_trace_information = None + + sentry_use_environment = ( + os.environ.get("SENTRY_USE_ENVIRONMENT") or "" + ).lower() + use_environment = sentry_use_environment not in FALSE_VALUES + if use_environment: + incoming_trace_information = {} + + if os.environ.get("SENTRY_TRACE"): + incoming_trace_information[SENTRY_TRACE_HEADER_NAME] = ( + os.environ.get("SENTRY_TRACE") or "" + ) + + if os.environ.get("SENTRY_BAGGAGE"): + incoming_trace_information[BAGGAGE_HEADER_NAME] = ( + os.environ.get("SENTRY_BAGGAGE") or "" + ) + + return incoming_trace_information or None + + def set_new_propagation_context(self): + # type: () -> None + """ + Creates a new propagation context and sets it as `_propagation_context`. Overwriting existing one. + """ + self._propagation_context = PropagationContext() + + def generate_propagation_context(self, incoming_data=None): + # type: (Optional[Dict[str, str]]) -> None + """ + Makes sure the propagation context is set on the scope. + If there is `incoming_data` overwrite existing propagation context. + If there is no `incoming_data` create new propagation context, but do NOT overwrite if already existing. + """ + if incoming_data: + propagation_context = PropagationContext.from_incoming_data(incoming_data) + if propagation_context is not None: + self._propagation_context = propagation_context + + if self._type != ScopeType.CURRENT: + if self._propagation_context is None: + self.set_new_propagation_context() + + def get_dynamic_sampling_context(self): + # type: () -> Optional[Dict[str, str]] + """ + Returns the Dynamic Sampling Context from the Propagation Context. + If not existing, creates a new one. + """ + if self._propagation_context is None: + return None + + baggage = self.get_baggage() + if baggage is not None: + self._propagation_context.dynamic_sampling_context = ( + baggage.dynamic_sampling_context() + ) + + return self._propagation_context.dynamic_sampling_context + + def get_traceparent(self, *args, **kwargs): + # type: (Any, Any) -> Optional[str] + """ + Returns the Sentry "sentry-trace" header (aka the traceparent) from the + currently active span or the scopes Propagation Context. + """ + client = self.get_client() + + # If we have an active span, return traceparent from there + if has_tracing_enabled(client.options) and self.span is not None: + return self.span.to_traceparent() + + # If this scope has a propagation context, return traceparent from there + if self._propagation_context is not None: + traceparent = "%s-%s" % ( + self._propagation_context.trace_id, + self._propagation_context.span_id, + ) + return traceparent + + # Fall back to isolation scope's traceparent. It always has one + return self.get_isolation_scope().get_traceparent() + + def get_baggage(self, *args, **kwargs): + # type: (Any, Any) -> Optional[Baggage] + """ + Returns the Sentry "baggage" header containing trace information from the + currently active span or the scopes Propagation Context. + """ + client = self.get_client() + + # If we have an active span, return baggage from there + if has_tracing_enabled(client.options) and self.span is not None: + return self.span.to_baggage() + + # If this scope has a propagation context, return baggage from there + if self._propagation_context is not None: + dynamic_sampling_context = ( + self._propagation_context.dynamic_sampling_context + ) + if dynamic_sampling_context is None: + return Baggage.from_options(self) + else: + return Baggage(dynamic_sampling_context) + + # Fall back to isolation scope's baggage. It always has one + return self.get_isolation_scope().get_baggage() + + def get_trace_context(self): + # type: () -> Any + """ + Returns the Sentry "trace" context from the Propagation Context. + """ + if self._propagation_context is None: + return None + + trace_context = { + "trace_id": self._propagation_context.trace_id, + "span_id": self._propagation_context.span_id, + "parent_span_id": self._propagation_context.parent_span_id, + "dynamic_sampling_context": self.get_dynamic_sampling_context(), + } # type: Dict[str, Any] + + return trace_context + + def trace_propagation_meta(self, *args, **kwargs): + # type: (*Any, **Any) -> str + """ + Return meta tags which should be injected into HTML templates + to allow propagation of trace information. + """ + span = kwargs.pop("span", None) + if span is not None: + logger.warning( + "The parameter `span` in trace_propagation_meta() is deprecated and will be removed in the future." + ) + + meta = "" + + sentry_trace = self.get_traceparent() + if sentry_trace is not None: + meta += '' % ( + SENTRY_TRACE_HEADER_NAME, + sentry_trace, + ) + + baggage = self.get_baggage() + if baggage is not None: + meta += '' % ( + BAGGAGE_HEADER_NAME, + baggage.serialize(), + ) + + return meta + + def iter_headers(self): + # type: () -> Iterator[Tuple[str, str]] + """ + Creates a generator which returns the `sentry-trace` and `baggage` headers from the Propagation Context. + """ + if self._propagation_context is not None: + traceparent = self.get_traceparent() + if traceparent is not None: + yield SENTRY_TRACE_HEADER_NAME, traceparent + + dsc = self.get_dynamic_sampling_context() + if dsc is not None: + baggage = Baggage(dsc).serialize() + yield BAGGAGE_HEADER_NAME, baggage + + def iter_trace_propagation_headers(self, *args, **kwargs): + # type: (Any, Any) -> Generator[Tuple[str, str], None, None] + """ + Return HTTP headers which allow propagation of trace data. + + If a span is given, the trace data will taken from the span. + If no span is given, the trace data is taken from the scope. + """ + client = self.get_client() + if not client.options.get("propagate_traces"): + return + + span = kwargs.pop("span", None) + span = span or self.span + + if has_tracing_enabled(client.options) and span is not None: + for header in span.iter_headers(): + yield header + else: + # If this scope has a propagation context, return headers from there + # (it could be that self is not the current scope nor the isolation scope) + if self._propagation_context is not None: + for header in self.iter_headers(): + yield header + else: + # otherwise try headers from current scope + current_scope = self.get_current_scope() + if current_scope._propagation_context is not None: + for header in current_scope.iter_headers(): + yield header + else: + # otherwise fall back to headers from isolation scope + isolation_scope = self.get_isolation_scope() + if isolation_scope._propagation_context is not None: + for header in isolation_scope.iter_headers(): + yield header + + def get_active_propagation_context(self): + # type: () -> Optional[PropagationContext] + if self._propagation_context is not None: + return self._propagation_context + + current_scope = self.get_current_scope() + if current_scope._propagation_context is not None: + return current_scope._propagation_context + + isolation_scope = self.get_isolation_scope() + if isolation_scope._propagation_context is not None: + return isolation_scope._propagation_context + + return None + + def clear(self): + # type: () -> None + """Clears the entire scope.""" + self._level = None # type: Optional[LogLevelStr] + self._fingerprint = None # type: Optional[List[str]] + self._transaction = None # type: Optional[str] + self._transaction_info = {} # type: MutableMapping[str, str] + self._user = None # type: Optional[Dict[str, Any]] + + self._tags = {} # type: Dict[str, Any] + self._contexts = {} # type: Dict[str, Dict[str, Any]] + self._extras = {} # type: MutableMapping[str, Any] + self._attachments = [] # type: List[Attachment] + + self.clear_breadcrumbs() + self._should_capture = True # type: bool + + self._span = None # type: Optional[Span] + self._session = None # type: Optional[Session] + self._force_auto_session_tracking = None # type: Optional[bool] + + self._profile = None # type: Optional[Profile] + + self._propagation_context = None + + # self._last_event_id is only applicable to isolation scopes + self._last_event_id = None # type: Optional[str] + self._flags = None # type: Optional[FlagBuffer] + + @_attr_setter + def level(self, value): + # type: (LogLevelStr) -> None + """ + When set this overrides the level. + + .. deprecated:: 1.0.0 + Use :func:`set_level` instead. + + :param value: The level to set. + """ + logger.warning( + "Deprecated: use .set_level() instead. This will be removed in the future." + ) + + self._level = value + + def set_level(self, value): + # type: (LogLevelStr) -> None + """ + Sets the level for the scope. + + :param value: The level to set. + """ + self._level = value + + @_attr_setter + def fingerprint(self, value): + # type: (Optional[List[str]]) -> None + """When set this overrides the default fingerprint.""" + self._fingerprint = value + + @property + def transaction(self): + # type: () -> Any + # would be type: () -> Optional[Transaction], see https://github.com/python/mypy/issues/3004 + """Return the transaction (root span) in the scope, if any.""" + + # there is no span/transaction on the scope + if self._span is None: + return None + + # there is an orphan span on the scope + if self._span.containing_transaction is None: + return None + + # there is either a transaction (which is its own containing + # transaction) or a non-orphan span on the scope + return self._span.containing_transaction + + @transaction.setter + def transaction(self, value): + # type: (Any) -> None + # would be type: (Optional[str]) -> None, see https://github.com/python/mypy/issues/3004 + """When set this forces a specific transaction name to be set. + + Deprecated: use set_transaction_name instead.""" + + # XXX: the docstring above is misleading. The implementation of + # apply_to_event prefers an existing value of event.transaction over + # anything set in the scope. + # XXX: note that with the introduction of the Scope.transaction getter, + # there is a semantic and type mismatch between getter and setter. The + # getter returns a Transaction, the setter sets a transaction name. + # Without breaking version compatibility, we could make the setter set a + # transaction name or transaction (self._span) depending on the type of + # the value argument. + + logger.warning( + "Assigning to scope.transaction directly is deprecated: use scope.set_transaction_name() instead." + ) + self._transaction = value + if self._span and self._span.containing_transaction: + self._span.containing_transaction.name = value + + def set_transaction_name(self, name, source=None): + # type: (str, Optional[str]) -> None + """Set the transaction name and optionally the transaction source.""" + self._transaction = name + + if self._span and self._span.containing_transaction: + self._span.containing_transaction.name = name + if source: + self._span.containing_transaction.source = source + + if source: + self._transaction_info["source"] = source + + @_attr_setter + def user(self, value): + # type: (Optional[Dict[str, Any]]) -> None + """When set a specific user is bound to the scope. Deprecated in favor of set_user.""" + self.set_user(value) + + def set_user(self, value): + # type: (Optional[Dict[str, Any]]) -> None + """Sets a user for the scope.""" + self._user = value + session = self.get_isolation_scope()._session + if session is not None: + session.update(user=value) + + @property + def span(self): + # type: () -> Optional[Span] + """Get/set current tracing span or transaction.""" + return self._span + + @span.setter + def span(self, span): + # type: (Optional[Span]) -> None + self._span = span + # XXX: this differs from the implementation in JS, there Scope.setSpan + # does not set Scope._transactionName. + if isinstance(span, Transaction): + transaction = span + if transaction.name: + self._transaction = transaction.name + if transaction.source: + self._transaction_info["source"] = transaction.source + + @property + def profile(self): + # type: () -> Optional[Profile] + return self._profile + + @profile.setter + def profile(self, profile): + # type: (Optional[Profile]) -> None + + self._profile = profile + + def set_tag(self, key, value): + # type: (str, Any) -> None + """ + Sets a tag for a key to a specific value. + + :param key: Key of the tag to set. + + :param value: Value of the tag to set. + """ + self._tags[key] = value + + def set_tags(self, tags): + # type: (Mapping[str, object]) -> None + """Sets multiple tags at once. + + This method updates multiple tags at once. The tags are passed as a dictionary + or other mapping type. + + Calling this method is equivalent to calling `set_tag` on each key-value pair + in the mapping. If a tag key already exists in the scope, its value will be + updated. If the tag key does not exist in the scope, the key-value pair will + be added to the scope. + + This method only modifies tag keys in the `tags` mapping passed to the method. + `scope.set_tags({})` is, therefore, a no-op. + + :param tags: A mapping of tag keys to tag values to set. + """ + self._tags.update(tags) + + def remove_tag(self, key): + # type: (str) -> None + """ + Removes a specific tag. + + :param key: Key of the tag to remove. + """ + self._tags.pop(key, None) + + def set_context( + self, + key, # type: str + value, # type: Dict[str, Any] + ): + # type: (...) -> None + """ + Binds a context at a certain key to a specific value. + """ + self._contexts[key] = value + + def remove_context( + self, key # type: str + ): + # type: (...) -> None + """Removes a context.""" + self._contexts.pop(key, None) + + def set_extra( + self, + key, # type: str + value, # type: Any + ): + # type: (...) -> None + """Sets an extra key to a specific value.""" + self._extras[key] = value + + def remove_extra( + self, key # type: str + ): + # type: (...) -> None + """Removes a specific extra key.""" + self._extras.pop(key, None) + + def clear_breadcrumbs(self): + # type: () -> None + """Clears breadcrumb buffer.""" + self._breadcrumbs = deque() # type: Deque[Breadcrumb] + + def add_attachment( + self, + bytes=None, # type: Union[None, bytes, Callable[[], bytes]] + filename=None, # type: Optional[str] + path=None, # type: Optional[str] + content_type=None, # type: Optional[str] + add_to_transactions=False, # type: bool + ): + # type: (...) -> None + """Adds an attachment to future events sent from this scope. + + The parameters are the same as for the :py:class:`sentry_sdk.attachments.Attachment` constructor. + """ + self._attachments.append( + Attachment( + bytes=bytes, + path=path, + filename=filename, + content_type=content_type, + add_to_transactions=add_to_transactions, + ) + ) + + def add_breadcrumb(self, crumb=None, hint=None, **kwargs): + # type: (Optional[Breadcrumb], Optional[BreadcrumbHint], Any) -> None + """ + Adds a breadcrumb. + + :param crumb: Dictionary with the data as the sentry v7/v8 protocol expects. + + :param hint: An optional value that can be used by `before_breadcrumb` + to customize the breadcrumbs that are emitted. + """ + client = self.get_client() + + if not client.is_active(): + logger.info("Dropped breadcrumb because no client bound") + return + + before_breadcrumb = client.options.get("before_breadcrumb") + max_breadcrumbs = client.options.get("max_breadcrumbs", DEFAULT_MAX_BREADCRUMBS) + + crumb = dict(crumb or ()) # type: Breadcrumb + crumb.update(kwargs) + if not crumb: + return + + hint = dict(hint or ()) # type: Hint + + if crumb.get("timestamp") is None: + crumb["timestamp"] = datetime.now(timezone.utc) + if crumb.get("type") is None: + crumb["type"] = "default" + + if before_breadcrumb is not None: + new_crumb = before_breadcrumb(crumb, hint) + else: + new_crumb = crumb + + if new_crumb is not None: + self._breadcrumbs.append(new_crumb) + else: + logger.info("before breadcrumb dropped breadcrumb (%s)", crumb) + + while len(self._breadcrumbs) > max_breadcrumbs: + self._breadcrumbs.popleft() + + def start_transaction( + self, + transaction=None, + instrumenter=INSTRUMENTER.SENTRY, + custom_sampling_context=None, + **kwargs, + ): + # type: (Optional[Transaction], str, Optional[SamplingContext], Unpack[TransactionKwargs]) -> Union[Transaction, NoOpSpan] + """ + Start and return a transaction. + + Start an existing transaction if given, otherwise create and start a new + transaction with kwargs. + + This is the entry point to manual tracing instrumentation. + + A tree structure can be built by adding child spans to the transaction, + and child spans to other spans. To start a new child span within the + transaction or any span, call the respective `.start_child()` method. + + Every child span must be finished before the transaction is finished, + otherwise the unfinished spans are discarded. + + When used as context managers, spans and transactions are automatically + finished at the end of the `with` block. If not using context managers, + call the `.finish()` method. + + When the transaction is finished, it will be sent to Sentry with all its + finished child spans. + + :param transaction: The transaction to start. If omitted, we create and + start a new transaction. + :param instrumenter: This parameter is meant for internal use only. It + will be removed in the next major version. + :param custom_sampling_context: The transaction's custom sampling context. + :param kwargs: Optional keyword arguments to be passed to the Transaction + constructor. See :py:class:`sentry_sdk.tracing.Transaction` for + available arguments. + """ + kwargs.setdefault("scope", self) + + client = self.get_client() + + configuration_instrumenter = client.options["instrumenter"] + + if instrumenter != configuration_instrumenter: + return NoOpSpan() + + try_autostart_continuous_profiler() + + custom_sampling_context = custom_sampling_context or {} + + # kwargs at this point has type TransactionKwargs, since we have removed + # the client and custom_sampling_context from it. + transaction_kwargs = kwargs # type: TransactionKwargs + + # if we haven't been given a transaction, make one + if transaction is None: + transaction = Transaction(**transaction_kwargs) + + # use traces_sample_rate, traces_sampler, and/or inheritance to make a + # sampling decision + sampling_context = { + "transaction_context": transaction.to_json(), + "parent_sampled": transaction.parent_sampled, + } + sampling_context.update(custom_sampling_context) + transaction._set_initial_sampling_decision(sampling_context=sampling_context) + + if transaction.sampled: + profile = Profile( + transaction.sampled, transaction._start_timestamp_monotonic_ns + ) + profile._set_initial_sampling_decision(sampling_context=sampling_context) + + transaction._profile = profile + + # we don't bother to keep spans if we already know we're not going to + # send the transaction + max_spans = (client.options["_experiments"].get("max_spans")) or 1000 + transaction.init_span_recorder(maxlen=max_spans) + + return transaction + + def start_span(self, instrumenter=INSTRUMENTER.SENTRY, **kwargs): + # type: (str, Any) -> Span + """ + Start a span whose parent is the currently active span or transaction, if any. + + The return value is a :py:class:`sentry_sdk.tracing.Span` instance, + typically used as a context manager to start and stop timing in a `with` + block. + + Only spans contained in a transaction are sent to Sentry. Most + integrations start a transaction at the appropriate time, for example + for every incoming HTTP request. Use + :py:meth:`sentry_sdk.start_transaction` to start a new transaction when + one is not already in progress. + + For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Span`. + + The instrumenter parameter is deprecated for user code, and it will + be removed in the next major version. Going forward, it should only + be used by the SDK itself. + """ + if kwargs.get("description") is not None: + warnings.warn( + "The `description` parameter is deprecated. Please use `name` instead.", + DeprecationWarning, + stacklevel=2, + ) + + with new_scope(): + kwargs.setdefault("scope", self) + + client = self.get_client() + + configuration_instrumenter = client.options["instrumenter"] + + if instrumenter != configuration_instrumenter: + return NoOpSpan() + + # get current span or transaction + span = self.span or self.get_isolation_scope().span + + if span is None: + # New spans get the `trace_id` from the scope + if "trace_id" not in kwargs: + propagation_context = self.get_active_propagation_context() + if propagation_context is not None: + kwargs["trace_id"] = propagation_context.trace_id + + span = Span(**kwargs) + else: + # Children take `trace_id`` from the parent span. + span = span.start_child(**kwargs) + + return span + + def continue_trace( + self, environ_or_headers, op=None, name=None, source=None, origin="manual" + ): + # type: (Dict[str, Any], Optional[str], Optional[str], Optional[str], str) -> Transaction + """ + Sets the propagation context from environment or headers and returns a transaction. + """ + self.generate_propagation_context(environ_or_headers) + + transaction = Transaction.continue_from_headers( + normalize_incoming_data(environ_or_headers), + op=op, + origin=origin, + name=name, + source=source, + ) + + return transaction + + def capture_event(self, event, hint=None, scope=None, **scope_kwargs): + # type: (Event, Optional[Hint], Optional[Scope], Any) -> Optional[str] + """ + Captures an event. + + Merges given scope data and calls :py:meth:`sentry_sdk.client._Client.capture_event`. + + :param event: A ready-made event that can be directly sent to Sentry. + + :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object. + + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :param scope_kwargs: Optional data to apply to event. + For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.client._Client.capture_event`). + """ + if disable_capture_event.get(False): + return None + + scope = self._merge_scopes(scope, scope_kwargs) + + event_id = self.get_client().capture_event(event=event, hint=hint, scope=scope) + + if event_id is not None and event.get("type") != "transaction": + self.get_isolation_scope()._last_event_id = event_id + + return event_id + + def capture_message(self, message, level=None, scope=None, **scope_kwargs): + # type: (str, Optional[LogLevelStr], Optional[Scope], Any) -> Optional[str] + """ + Captures a message. + + :param message: The string to send as the message. + + :param level: If no level is provided, the default level is `info`. + + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :param scope_kwargs: Optional data to apply to event. + For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.client._Client.capture_event`). + """ + if disable_capture_event.get(False): + return None + + if level is None: + level = "info" + + event = { + "message": message, + "level": level, + } # type: Event + + return self.capture_event(event, scope=scope, **scope_kwargs) + + def capture_exception(self, error=None, scope=None, **scope_kwargs): + # type: (Optional[Union[BaseException, ExcInfo]], Optional[Scope], Any) -> Optional[str] + """Captures an exception. + + :param error: An exception to capture. If `None`, `sys.exc_info()` will be used. + + :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :param scope_kwargs: Optional data to apply to event. + For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`. + The `scope` and `scope_kwargs` parameters are mutually exclusive. + + :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.client._Client.capture_event`). + """ + if disable_capture_event.get(False): + return None + + if error is not None: + exc_info = exc_info_from_error(error) + else: + exc_info = sys.exc_info() + + event, hint = event_from_exception( + exc_info, client_options=self.get_client().options + ) + + try: + return self.capture_event(event, hint=hint, scope=scope, **scope_kwargs) + except Exception: + capture_internal_exception(sys.exc_info()) + + return None + + def start_session(self, *args, **kwargs): + # type: (*Any, **Any) -> None + """Starts a new session.""" + session_mode = kwargs.pop("session_mode", "application") + + self.end_session() + + client = self.get_client() + self._session = Session( + release=client.options.get("release"), + environment=client.options.get("environment"), + user=self._user, + session_mode=session_mode, + ) + + def end_session(self, *args, **kwargs): + # type: (*Any, **Any) -> None + """Ends the current session if there is one.""" + session = self._session + self._session = None + + if session is not None: + session.close() + self.get_client().capture_session(session) + + def stop_auto_session_tracking(self, *args, **kwargs): + # type: (*Any, **Any) -> None + """Stops automatic session tracking. + + This temporarily session tracking for the current scope when called. + To resume session tracking call `resume_auto_session_tracking`. + """ + self.end_session() + self._force_auto_session_tracking = False + + def resume_auto_session_tracking(self): + # type: (...) -> None + """Resumes automatic session tracking for the current scope if + disabled earlier. This requires that generally automatic session + tracking is enabled. + """ + self._force_auto_session_tracking = None + + def add_event_processor( + self, func # type: EventProcessor + ): + # type: (...) -> None + """Register a scope local event processor on the scope. + + :param func: This function behaves like `before_send.` + """ + if len(self._event_processors) > 20: + logger.warning( + "Too many event processors on scope! Clearing list to free up some memory: %r", + self._event_processors, + ) + del self._event_processors[:] + + self._event_processors.append(func) + + def add_error_processor( + self, + func, # type: ErrorProcessor + cls=None, # type: Optional[Type[BaseException]] + ): + # type: (...) -> None + """Register a scope local error processor on the scope. + + :param func: A callback that works similar to an event processor but is invoked with the original exception info triple as second argument. + + :param cls: Optionally, only process exceptions of this type. + """ + if cls is not None: + cls_ = cls # For mypy. + real_func = func + + def func(event, exc_info): + # type: (Event, ExcInfo) -> Optional[Event] + try: + is_inst = isinstance(exc_info[1], cls_) + except Exception: + is_inst = False + if is_inst: + return real_func(event, exc_info) + return event + + self._error_processors.append(func) + + def _apply_level_to_event(self, event, hint, options): + # type: (Event, Hint, Optional[Dict[str, Any]]) -> None + if self._level is not None: + event["level"] = self._level + + def _apply_breadcrumbs_to_event(self, event, hint, options): + # type: (Event, Hint, Optional[Dict[str, Any]]) -> None + event.setdefault("breadcrumbs", {}).setdefault("values", []).extend( + self._breadcrumbs + ) + + # Attempt to sort timestamps + try: + for crumb in event["breadcrumbs"]["values"]: + if isinstance(crumb["timestamp"], str): + crumb["timestamp"] = datetime_from_isoformat(crumb["timestamp"]) + + event["breadcrumbs"]["values"].sort(key=lambda crumb: crumb["timestamp"]) + except Exception as err: + logger.debug("Error when sorting breadcrumbs", exc_info=err) + pass + + def _apply_user_to_event(self, event, hint, options): + # type: (Event, Hint, Optional[Dict[str, Any]]) -> None + if event.get("user") is None and self._user is not None: + event["user"] = self._user + + def _apply_transaction_name_to_event(self, event, hint, options): + # type: (Event, Hint, Optional[Dict[str, Any]]) -> None + if event.get("transaction") is None and self._transaction is not None: + event["transaction"] = self._transaction + + def _apply_transaction_info_to_event(self, event, hint, options): + # type: (Event, Hint, Optional[Dict[str, Any]]) -> None + if event.get("transaction_info") is None and self._transaction_info is not None: + event["transaction_info"] = self._transaction_info + + def _apply_fingerprint_to_event(self, event, hint, options): + # type: (Event, Hint, Optional[Dict[str, Any]]) -> None + if event.get("fingerprint") is None and self._fingerprint is not None: + event["fingerprint"] = self._fingerprint + + def _apply_extra_to_event(self, event, hint, options): + # type: (Event, Hint, Optional[Dict[str, Any]]) -> None + if self._extras: + event.setdefault("extra", {}).update(self._extras) + + def _apply_tags_to_event(self, event, hint, options): + # type: (Event, Hint, Optional[Dict[str, Any]]) -> None + if self._tags: + event.setdefault("tags", {}).update(self._tags) + + def _apply_contexts_to_event(self, event, hint, options): + # type: (Event, Hint, Optional[Dict[str, Any]]) -> None + if self._contexts: + event.setdefault("contexts", {}).update(self._contexts) + + contexts = event.setdefault("contexts", {}) + + # Add "trace" context + if contexts.get("trace") is None: + if has_tracing_enabled(options) and self._span is not None: + contexts["trace"] = self._span.get_trace_context() + else: + contexts["trace"] = self.get_trace_context() + + def _apply_flags_to_event(self, event, hint, options): + # type: (Event, Hint, Optional[Dict[str, Any]]) -> None + flags = self.flags.get() + if len(flags) > 0: + event.setdefault("contexts", {}).setdefault("flags", {}).update( + {"values": flags} + ) + + def _drop(self, cause, ty): + # type: (Any, str) -> Optional[Any] + logger.info("%s (%s) dropped event", ty, cause) + return None + + def run_error_processors(self, event, hint): + # type: (Event, Hint) -> Optional[Event] + """ + Runs the error processors on the event and returns the modified event. + """ + exc_info = hint.get("exc_info") + if exc_info is not None: + error_processors = chain( + self.get_global_scope()._error_processors, + self.get_isolation_scope()._error_processors, + self.get_current_scope()._error_processors, + ) + + for error_processor in error_processors: + new_event = error_processor(event, exc_info) + if new_event is None: + return self._drop(error_processor, "error processor") + + event = new_event + + return event + + def run_event_processors(self, event, hint): + # type: (Event, Hint) -> Optional[Event] + """ + Runs the event processors on the event and returns the modified event. + """ + ty = event.get("type") + is_check_in = ty == "check_in" + + if not is_check_in: + # Get scopes without creating them to prevent infinite recursion + isolation_scope = _isolation_scope.get() + current_scope = _current_scope.get() + + event_processors = chain( + global_event_processors, + _global_scope and _global_scope._event_processors or [], + isolation_scope and isolation_scope._event_processors or [], + current_scope and current_scope._event_processors or [], + ) + + for event_processor in event_processors: + new_event = event + with capture_internal_exceptions(): + new_event = event_processor(event, hint) + if new_event is None: + return self._drop(event_processor, "event processor") + event = new_event + + return event + + @_disable_capture + def apply_to_event( + self, + event, # type: Event + hint, # type: Hint + options=None, # type: Optional[Dict[str, Any]] + ): + # type: (...) -> Optional[Event] + """Applies the information contained on the scope to the given event.""" + ty = event.get("type") + is_transaction = ty == "transaction" + is_check_in = ty == "check_in" + + # put all attachments into the hint. This lets callbacks play around + # with attachments. We also later pull this out of the hint when we + # create the envelope. + attachments_to_send = hint.get("attachments") or [] + for attachment in self._attachments: + if not is_transaction or attachment.add_to_transactions: + attachments_to_send.append(attachment) + hint["attachments"] = attachments_to_send + + self._apply_contexts_to_event(event, hint, options) + + if is_check_in: + # Check-ins only support the trace context, strip all others + event["contexts"] = { + "trace": event.setdefault("contexts", {}).get("trace", {}) + } + + if not is_check_in: + self._apply_level_to_event(event, hint, options) + self._apply_fingerprint_to_event(event, hint, options) + self._apply_user_to_event(event, hint, options) + self._apply_transaction_name_to_event(event, hint, options) + self._apply_transaction_info_to_event(event, hint, options) + self._apply_tags_to_event(event, hint, options) + self._apply_extra_to_event(event, hint, options) + + if not is_transaction and not is_check_in: + self._apply_breadcrumbs_to_event(event, hint, options) + self._apply_flags_to_event(event, hint, options) + + event = self.run_error_processors(event, hint) + if event is None: + return None + + event = self.run_event_processors(event, hint) + if event is None: + return None + + return event + + def update_from_scope(self, scope): + # type: (Scope) -> None + """Update the scope with another scope's data.""" + if scope._level is not None: + self._level = scope._level + if scope._fingerprint is not None: + self._fingerprint = scope._fingerprint + if scope._transaction is not None: + self._transaction = scope._transaction + if scope._transaction_info is not None: + self._transaction_info.update(scope._transaction_info) + if scope._user is not None: + self._user = scope._user + if scope._tags: + self._tags.update(scope._tags) + if scope._contexts: + self._contexts.update(scope._contexts) + if scope._extras: + self._extras.update(scope._extras) + if scope._breadcrumbs: + self._breadcrumbs.extend(scope._breadcrumbs) + if scope._span: + self._span = scope._span + if scope._attachments: + self._attachments.extend(scope._attachments) + if scope._profile: + self._profile = scope._profile + if scope._propagation_context: + self._propagation_context = scope._propagation_context + if scope._session: + self._session = scope._session + if scope._flags: + if not self._flags: + self._flags = deepcopy(scope._flags) + else: + for flag in scope._flags.get(): + self._flags.set(flag["flag"], flag["result"]) + + def update_from_kwargs( + self, + user=None, # type: Optional[Any] + level=None, # type: Optional[LogLevelStr] + extras=None, # type: Optional[Dict[str, Any]] + contexts=None, # type: Optional[Dict[str, Any]] + tags=None, # type: Optional[Dict[str, str]] + fingerprint=None, # type: Optional[List[str]] + ): + # type: (...) -> None + """Update the scope's attributes.""" + if level is not None: + self._level = level + if user is not None: + self._user = user + if extras is not None: + self._extras.update(extras) + if contexts is not None: + self._contexts.update(contexts) + if tags is not None: + self._tags.update(tags) + if fingerprint is not None: + self._fingerprint = fingerprint + + def __repr__(self): + # type: () -> str + return "<%s id=%s name=%s type=%s>" % ( + self.__class__.__name__, + hex(id(self)), + self._name, + self._type, + ) + + @property + def flags(self): + # type: () -> FlagBuffer + if self._flags is None: + max_flags = ( + self.get_client().options["_experiments"].get("max_flags") + or DEFAULT_FLAG_CAPACITY + ) + self._flags = FlagBuffer(capacity=max_flags) + return self._flags + + +@contextmanager +def new_scope(): + # type: () -> Generator[Scope, None, None] + """ + .. versionadded:: 2.0.0 + + Context manager that forks the current scope and runs the wrapped code in it. + After the wrapped code is executed, the original scope is restored. + + Example Usage: + + .. code-block:: python + + import sentry_sdk + + with sentry_sdk.new_scope() as scope: + scope.set_tag("color", "green") + sentry_sdk.capture_message("hello") # will include `color` tag. + + sentry_sdk.capture_message("hello, again") # will NOT include `color` tag. + + """ + # fork current scope + current_scope = Scope.get_current_scope() + new_scope = current_scope.fork() + token = _current_scope.set(new_scope) + + try: + yield new_scope + + finally: + # restore original scope + _current_scope.reset(token) + + +@contextmanager +def use_scope(scope): + # type: (Scope) -> Generator[Scope, None, None] + """ + .. versionadded:: 2.0.0 + + Context manager that uses the given `scope` and runs the wrapped code in it. + After the wrapped code is executed, the original scope is restored. + + Example Usage: + Suppose the variable `scope` contains a `Scope` object, which is not currently + the active scope. + + .. code-block:: python + + import sentry_sdk + + with sentry_sdk.use_scope(scope): + scope.set_tag("color", "green") + sentry_sdk.capture_message("hello") # will include `color` tag. + + sentry_sdk.capture_message("hello, again") # will NOT include `color` tag. + + """ + # set given scope as current scope + token = _current_scope.set(scope) + + try: + yield scope + + finally: + # restore original scope + _current_scope.reset(token) + + +@contextmanager +def isolation_scope(): + # type: () -> Generator[Scope, None, None] + """ + .. versionadded:: 2.0.0 + + Context manager that forks the current isolation scope and runs the wrapped code in it. + The current scope is also forked to not bleed data into the existing current scope. + After the wrapped code is executed, the original scopes are restored. + + Example Usage: + + .. code-block:: python + + import sentry_sdk + + with sentry_sdk.isolation_scope() as scope: + scope.set_tag("color", "green") + sentry_sdk.capture_message("hello") # will include `color` tag. + + sentry_sdk.capture_message("hello, again") # will NOT include `color` tag. + + """ + # fork current scope + current_scope = Scope.get_current_scope() + forked_current_scope = current_scope.fork() + current_token = _current_scope.set(forked_current_scope) + + # fork isolation scope + isolation_scope = Scope.get_isolation_scope() + new_isolation_scope = isolation_scope.fork() + isolation_token = _isolation_scope.set(new_isolation_scope) + + try: + yield new_isolation_scope + + finally: + # restore original scopes + _current_scope.reset(current_token) + _isolation_scope.reset(isolation_token) + + +@contextmanager +def use_isolation_scope(isolation_scope): + # type: (Scope) -> Generator[Scope, None, None] + """ + .. versionadded:: 2.0.0 + + Context manager that uses the given `isolation_scope` and runs the wrapped code in it. + The current scope is also forked to not bleed data into the existing current scope. + After the wrapped code is executed, the original scopes are restored. + + Example Usage: + + .. code-block:: python + + import sentry_sdk + + with sentry_sdk.isolation_scope() as scope: + scope.set_tag("color", "green") + sentry_sdk.capture_message("hello") # will include `color` tag. + + sentry_sdk.capture_message("hello, again") # will NOT include `color` tag. + + """ + # fork current scope + current_scope = Scope.get_current_scope() + forked_current_scope = current_scope.fork() + current_token = _current_scope.set(forked_current_scope) + + # set given scope as isolation scope + isolation_token = _isolation_scope.set(isolation_scope) + + try: + yield isolation_scope + + finally: + # restore original scopes + _current_scope.reset(current_token) + _isolation_scope.reset(isolation_token) + + +def should_send_default_pii(): + # type: () -> bool + """Shortcut for `Scope.get_client().should_send_default_pii()`.""" + return Scope.get_client().should_send_default_pii() + + +# Circular imports +from sentry_sdk.client import NonRecordingClient + +if TYPE_CHECKING: + import sentry_sdk.client diff --git a/aws/lambda_demo/sentry_sdk/scrubber.py b/aws/lambda_demo/sentry_sdk/scrubber.py new file mode 100644 index 000000000..f4755ea93 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/scrubber.py @@ -0,0 +1,175 @@ +from sentry_sdk.utils import ( + capture_internal_exceptions, + AnnotatedValue, + iter_event_frames, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from sentry_sdk._types import Event + from typing import List + from typing import Optional + + +DEFAULT_DENYLIST = [ + # stolen from relay + "password", + "passwd", + "secret", + "api_key", + "apikey", + "auth", + "credentials", + "mysql_pwd", + "privatekey", + "private_key", + "token", + "session", + # django + "csrftoken", + "sessionid", + # wsgi + "x_csrftoken", + "x_forwarded_for", + "set_cookie", + "cookie", + "authorization", + "x_api_key", + # other common names used in the wild + "aiohttp_session", # aiohttp + "connect.sid", # Express + "csrf_token", # Pyramid + "csrf", # (this is a cookie name used in accepted answers on stack overflow) + "_csrf", # Express + "_csrf_token", # Bottle + "PHPSESSID", # PHP + "_session", # Sanic + "symfony", # Symfony + "user_session", # Vue + "_xsrf", # Tornado + "XSRF-TOKEN", # Angular, Laravel +] + +DEFAULT_PII_DENYLIST = [ + "x_forwarded_for", + "x_real_ip", + "ip_address", + "remote_addr", +] + + +class EventScrubber: + def __init__( + self, denylist=None, recursive=False, send_default_pii=False, pii_denylist=None + ): + # type: (Optional[List[str]], bool, bool, Optional[List[str]]) -> None + """ + A scrubber that goes through the event payload and removes sensitive data configured through denylists. + + :param denylist: A security denylist that is always scrubbed, defaults to DEFAULT_DENYLIST. + :param recursive: Whether to scrub the event payload recursively, default False. + :param send_default_pii: Whether pii is sending is on, pii fields are not scrubbed. + :param pii_denylist: The denylist to use for scrubbing when pii is not sent, defaults to DEFAULT_PII_DENYLIST. + """ + self.denylist = DEFAULT_DENYLIST.copy() if denylist is None else denylist + + if not send_default_pii: + pii_denylist = ( + DEFAULT_PII_DENYLIST.copy() if pii_denylist is None else pii_denylist + ) + self.denylist += pii_denylist + + self.denylist = [x.lower() for x in self.denylist] + self.recursive = recursive + + def scrub_list(self, lst): + # type: (object) -> None + """ + If a list is passed to this method, the method recursively searches the list and any + nested lists for any dictionaries. The method calls scrub_dict on all dictionaries + it finds. + If the parameter passed to this method is not a list, the method does nothing. + """ + if not isinstance(lst, list): + return + + for v in lst: + self.scrub_dict(v) # no-op unless v is a dict + self.scrub_list(v) # no-op unless v is a list + + def scrub_dict(self, d): + # type: (object) -> None + """ + If a dictionary is passed to this method, the method scrubs the dictionary of any + sensitive data. The method calls itself recursively on any nested dictionaries ( + including dictionaries nested in lists) if self.recursive is True. + This method does nothing if the parameter passed to it is not a dictionary. + """ + if not isinstance(d, dict): + return + + for k, v in d.items(): + # The cast is needed because mypy is not smart enough to figure out that k must be a + # string after the isinstance check. + if isinstance(k, str) and k.lower() in self.denylist: + d[k] = AnnotatedValue.substituted_because_contains_sensitive_data() + elif self.recursive: + self.scrub_dict(v) # no-op unless v is a dict + self.scrub_list(v) # no-op unless v is a list + + def scrub_request(self, event): + # type: (Event) -> None + with capture_internal_exceptions(): + if "request" in event: + if "headers" in event["request"]: + self.scrub_dict(event["request"]["headers"]) + if "cookies" in event["request"]: + self.scrub_dict(event["request"]["cookies"]) + if "data" in event["request"]: + self.scrub_dict(event["request"]["data"]) + + def scrub_extra(self, event): + # type: (Event) -> None + with capture_internal_exceptions(): + if "extra" in event: + self.scrub_dict(event["extra"]) + + def scrub_user(self, event): + # type: (Event) -> None + with capture_internal_exceptions(): + if "user" in event: + self.scrub_dict(event["user"]) + + def scrub_breadcrumbs(self, event): + # type: (Event) -> None + with capture_internal_exceptions(): + if "breadcrumbs" in event: + if "values" in event["breadcrumbs"]: + for value in event["breadcrumbs"]["values"]: + if "data" in value: + self.scrub_dict(value["data"]) + + def scrub_frames(self, event): + # type: (Event) -> None + with capture_internal_exceptions(): + for frame in iter_event_frames(event): + if "vars" in frame: + self.scrub_dict(frame["vars"]) + + def scrub_spans(self, event): + # type: (Event) -> None + with capture_internal_exceptions(): + if "spans" in event: + for span in event["spans"]: + if "data" in span: + self.scrub_dict(span["data"]) + + def scrub_event(self, event): + # type: (Event) -> None + self.scrub_request(event) + self.scrub_extra(event) + self.scrub_user(event) + self.scrub_breadcrumbs(event) + self.scrub_frames(event) + self.scrub_spans(event) diff --git a/aws/lambda_demo/sentry_sdk/serializer.py b/aws/lambda_demo/sentry_sdk/serializer.py new file mode 100644 index 000000000..bc8e38c63 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/serializer.py @@ -0,0 +1,388 @@ +import sys +import math +from collections.abc import Mapping, Sequence, Set +from datetime import datetime + +from sentry_sdk.utils import ( + AnnotatedValue, + capture_internal_exception, + disable_capture_event, + format_timestamp, + safe_repr, + strip_string, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from types import TracebackType + + from typing import Any + from typing import Callable + from typing import ContextManager + from typing import Dict + from typing import List + from typing import Optional + from typing import Type + from typing import Union + + from sentry_sdk._types import NotImplementedType + + Span = Dict[str, Any] + + ReprProcessor = Callable[[Any, Dict[str, Any]], Union[NotImplementedType, str]] + Segment = Union[str, int] + + +# Bytes are technically not strings in Python 3, but we can serialize them +serializable_str_types = (str, bytes, bytearray, memoryview) + + +# Maximum length of JSON-serialized event payloads that can be safely sent +# before the server may reject the event due to its size. This is not intended +# to reflect actual values defined server-side, but rather only be an upper +# bound for events sent by the SDK. +# +# Can be overwritten if wanting to send more bytes, e.g. with a custom server. +# When changing this, keep in mind that events may be a little bit larger than +# this value due to attached metadata, so keep the number conservative. +MAX_EVENT_BYTES = 10**6 + +# Maximum depth and breadth of databags. Excess data will be trimmed. If +# max_request_body_size is "always", request bodies won't be trimmed. +MAX_DATABAG_DEPTH = 5 +MAX_DATABAG_BREADTH = 10 +CYCLE_MARKER = "" + + +global_repr_processors = [] # type: List[ReprProcessor] + + +def add_global_repr_processor(processor): + # type: (ReprProcessor) -> None + global_repr_processors.append(processor) + + +class Memo: + __slots__ = ("_ids", "_objs") + + def __init__(self): + # type: () -> None + self._ids = {} # type: Dict[int, Any] + self._objs = [] # type: List[Any] + + def memoize(self, obj): + # type: (Any) -> ContextManager[bool] + self._objs.append(obj) + return self + + def __enter__(self): + # type: () -> bool + obj = self._objs[-1] + if id(obj) in self._ids: + return True + else: + self._ids[id(obj)] = obj + return False + + def __exit__( + self, + ty, # type: Optional[Type[BaseException]] + value, # type: Optional[BaseException] + tb, # type: Optional[TracebackType] + ): + # type: (...) -> None + self._ids.pop(id(self._objs.pop()), None) + + +def serialize(event, **kwargs): + # type: (Dict[str, Any], **Any) -> Dict[str, Any] + """ + A very smart serializer that takes a dict and emits a json-friendly dict. + Currently used for serializing the final Event and also prematurely while fetching the stack + local variables for each frame in a stacktrace. + + It works internally with 'databags' which are arbitrary data structures like Mapping, Sequence and Set. + The algorithm itself is a recursive graph walk down the data structures it encounters. + + It has the following responsibilities: + * Trimming databags and keeping them within MAX_DATABAG_BREADTH and MAX_DATABAG_DEPTH. + * Calling safe_repr() on objects appropriately to keep them informative and readable in the final payload. + * Annotating the payload with the _meta field whenever trimming happens. + + :param max_request_body_size: If set to "always", will never trim request bodies. + :param max_value_length: The max length to strip strings to, defaults to sentry_sdk.consts.DEFAULT_MAX_VALUE_LENGTH + :param is_vars: If we're serializing vars early, we want to repr() things that are JSON-serializable to make their type more apparent. For example, it's useful to see the difference between a unicode-string and a bytestring when viewing a stacktrace. + :param custom_repr: A custom repr function that runs before safe_repr on the object to be serialized. If it returns None or throws internally, we will fallback to safe_repr. + + """ + memo = Memo() + path = [] # type: List[Segment] + meta_stack = [] # type: List[Dict[str, Any]] + + keep_request_bodies = ( + kwargs.pop("max_request_body_size", None) == "always" + ) # type: bool + max_value_length = kwargs.pop("max_value_length", None) # type: Optional[int] + is_vars = kwargs.pop("is_vars", False) + custom_repr = kwargs.pop("custom_repr", None) # type: Callable[..., Optional[str]] + + def _safe_repr_wrapper(value): + # type: (Any) -> str + try: + repr_value = None + if custom_repr is not None: + repr_value = custom_repr(value) + return repr_value or safe_repr(value) + except Exception: + return safe_repr(value) + + def _annotate(**meta): + # type: (**Any) -> None + while len(meta_stack) <= len(path): + try: + segment = path[len(meta_stack) - 1] + node = meta_stack[-1].setdefault(str(segment), {}) + except IndexError: + node = {} + + meta_stack.append(node) + + meta_stack[-1].setdefault("", {}).update(meta) + + def _is_databag(): + # type: () -> Optional[bool] + """ + A databag is any value that we need to trim. + True for stuff like vars, request bodies, breadcrumbs and extra. + + :returns: `True` for "yes", `False` for :"no", `None` for "maybe soon". + """ + try: + if is_vars: + return True + + is_request_body = _is_request_body() + if is_request_body in (True, None): + return is_request_body + + p0 = path[0] + if p0 == "breadcrumbs" and path[1] == "values": + path[2] + return True + + if p0 == "extra": + return True + + except IndexError: + return None + + return False + + def _is_request_body(): + # type: () -> Optional[bool] + try: + if path[0] == "request" and path[1] == "data": + return True + except IndexError: + return None + + return False + + def _serialize_node( + obj, # type: Any + is_databag=None, # type: Optional[bool] + is_request_body=None, # type: Optional[bool] + should_repr_strings=None, # type: Optional[bool] + segment=None, # type: Optional[Segment] + remaining_breadth=None, # type: Optional[Union[int, float]] + remaining_depth=None, # type: Optional[Union[int, float]] + ): + # type: (...) -> Any + if segment is not None: + path.append(segment) + + try: + with memo.memoize(obj) as result: + if result: + return CYCLE_MARKER + + return _serialize_node_impl( + obj, + is_databag=is_databag, + is_request_body=is_request_body, + should_repr_strings=should_repr_strings, + remaining_depth=remaining_depth, + remaining_breadth=remaining_breadth, + ) + except BaseException: + capture_internal_exception(sys.exc_info()) + + if is_databag: + return "" + + return None + finally: + if segment is not None: + path.pop() + del meta_stack[len(path) + 1 :] + + def _flatten_annotated(obj): + # type: (Any) -> Any + if isinstance(obj, AnnotatedValue): + _annotate(**obj.metadata) + obj = obj.value + return obj + + def _serialize_node_impl( + obj, + is_databag, + is_request_body, + should_repr_strings, + remaining_depth, + remaining_breadth, + ): + # type: (Any, Optional[bool], Optional[bool], Optional[bool], Optional[Union[float, int]], Optional[Union[float, int]]) -> Any + if isinstance(obj, AnnotatedValue): + should_repr_strings = False + if should_repr_strings is None: + should_repr_strings = is_vars + + if is_databag is None: + is_databag = _is_databag() + + if is_request_body is None: + is_request_body = _is_request_body() + + if is_databag: + if is_request_body and keep_request_bodies: + remaining_depth = float("inf") + remaining_breadth = float("inf") + else: + if remaining_depth is None: + remaining_depth = MAX_DATABAG_DEPTH + if remaining_breadth is None: + remaining_breadth = MAX_DATABAG_BREADTH + + obj = _flatten_annotated(obj) + + if remaining_depth is not None and remaining_depth <= 0: + _annotate(rem=[["!limit", "x"]]) + if is_databag: + return _flatten_annotated( + strip_string(_safe_repr_wrapper(obj), max_length=max_value_length) + ) + return None + + if is_databag and global_repr_processors: + hints = {"memo": memo, "remaining_depth": remaining_depth} + for processor in global_repr_processors: + result = processor(obj, hints) + if result is not NotImplemented: + return _flatten_annotated(result) + + sentry_repr = getattr(type(obj), "__sentry_repr__", None) + + if obj is None or isinstance(obj, (bool, int, float)): + if should_repr_strings or ( + isinstance(obj, float) and (math.isinf(obj) or math.isnan(obj)) + ): + return _safe_repr_wrapper(obj) + else: + return obj + + elif callable(sentry_repr): + return sentry_repr(obj) + + elif isinstance(obj, datetime): + return ( + str(format_timestamp(obj)) + if not should_repr_strings + else _safe_repr_wrapper(obj) + ) + + elif isinstance(obj, Mapping): + # Create temporary copy here to avoid calling too much code that + # might mutate our dictionary while we're still iterating over it. + obj = dict(obj.items()) + + rv_dict = {} # type: Dict[str, Any] + i = 0 + + for k, v in obj.items(): + if remaining_breadth is not None and i >= remaining_breadth: + _annotate(len=len(obj)) + break + + str_k = str(k) + v = _serialize_node( + v, + segment=str_k, + should_repr_strings=should_repr_strings, + is_databag=is_databag, + is_request_body=is_request_body, + remaining_depth=( + remaining_depth - 1 if remaining_depth is not None else None + ), + remaining_breadth=remaining_breadth, + ) + rv_dict[str_k] = v + i += 1 + + return rv_dict + + elif not isinstance(obj, serializable_str_types) and isinstance( + obj, (Set, Sequence) + ): + rv_list = [] + + for i, v in enumerate(obj): + if remaining_breadth is not None and i >= remaining_breadth: + _annotate(len=len(obj)) + break + + rv_list.append( + _serialize_node( + v, + segment=i, + should_repr_strings=should_repr_strings, + is_databag=is_databag, + is_request_body=is_request_body, + remaining_depth=( + remaining_depth - 1 if remaining_depth is not None else None + ), + remaining_breadth=remaining_breadth, + ) + ) + + return rv_list + + if should_repr_strings: + obj = _safe_repr_wrapper(obj) + else: + if isinstance(obj, bytes) or isinstance(obj, bytearray): + obj = obj.decode("utf-8", "replace") + + if not isinstance(obj, str): + obj = _safe_repr_wrapper(obj) + + is_span_description = ( + len(path) == 3 and path[0] == "spans" and path[-1] == "description" + ) + if is_span_description: + return obj + + return _flatten_annotated(strip_string(obj, max_length=max_value_length)) + + # + # Start of serialize() function + # + disable_capture_event.set(True) + try: + serialized_event = _serialize_node(event, **kwargs) + if not is_vars and meta_stack and isinstance(serialized_event, dict): + serialized_event["_meta"] = meta_stack[0] + + return serialized_event + finally: + disable_capture_event.set(False) diff --git a/aws/lambda_demo/sentry_sdk/session.py b/aws/lambda_demo/sentry_sdk/session.py new file mode 100644 index 000000000..c1d422c11 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/session.py @@ -0,0 +1,175 @@ +import uuid +from datetime import datetime, timezone + +from sentry_sdk.utils import format_timestamp + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Optional + from typing import Union + from typing import Any + from typing import Dict + + from sentry_sdk._types import SessionStatus + + +def _minute_trunc(ts): + # type: (datetime) -> datetime + return ts.replace(second=0, microsecond=0) + + +def _make_uuid( + val, # type: Union[str, uuid.UUID] +): + # type: (...) -> uuid.UUID + if isinstance(val, uuid.UUID): + return val + return uuid.UUID(val) + + +class Session: + def __init__( + self, + sid=None, # type: Optional[Union[str, uuid.UUID]] + did=None, # type: Optional[str] + timestamp=None, # type: Optional[datetime] + started=None, # type: Optional[datetime] + duration=None, # type: Optional[float] + status=None, # type: Optional[SessionStatus] + release=None, # type: Optional[str] + environment=None, # type: Optional[str] + user_agent=None, # type: Optional[str] + ip_address=None, # type: Optional[str] + errors=None, # type: Optional[int] + user=None, # type: Optional[Any] + session_mode="application", # type: str + ): + # type: (...) -> None + if sid is None: + sid = uuid.uuid4() + if started is None: + started = datetime.now(timezone.utc) + if status is None: + status = "ok" + self.status = status + self.did = None # type: Optional[str] + self.started = started + self.release = None # type: Optional[str] + self.environment = None # type: Optional[str] + self.duration = None # type: Optional[float] + self.user_agent = None # type: Optional[str] + self.ip_address = None # type: Optional[str] + self.session_mode = session_mode # type: str + self.errors = 0 + + self.update( + sid=sid, + did=did, + timestamp=timestamp, + duration=duration, + release=release, + environment=environment, + user_agent=user_agent, + ip_address=ip_address, + errors=errors, + user=user, + ) + + @property + def truncated_started(self): + # type: (...) -> datetime + return _minute_trunc(self.started) + + def update( + self, + sid=None, # type: Optional[Union[str, uuid.UUID]] + did=None, # type: Optional[str] + timestamp=None, # type: Optional[datetime] + started=None, # type: Optional[datetime] + duration=None, # type: Optional[float] + status=None, # type: Optional[SessionStatus] + release=None, # type: Optional[str] + environment=None, # type: Optional[str] + user_agent=None, # type: Optional[str] + ip_address=None, # type: Optional[str] + errors=None, # type: Optional[int] + user=None, # type: Optional[Any] + ): + # type: (...) -> None + # If a user is supplied we pull some data form it + if user: + if ip_address is None: + ip_address = user.get("ip_address") + if did is None: + did = user.get("id") or user.get("email") or user.get("username") + + if sid is not None: + self.sid = _make_uuid(sid) + if did is not None: + self.did = str(did) + if timestamp is None: + timestamp = datetime.now(timezone.utc) + self.timestamp = timestamp + if started is not None: + self.started = started + if duration is not None: + self.duration = duration + if release is not None: + self.release = release + if environment is not None: + self.environment = environment + if ip_address is not None: + self.ip_address = ip_address + if user_agent is not None: + self.user_agent = user_agent + if errors is not None: + self.errors = errors + + if status is not None: + self.status = status + + def close( + self, status=None # type: Optional[SessionStatus] + ): + # type: (...) -> Any + if status is None and self.status == "ok": + status = "exited" + if status is not None: + self.update(status=status) + + def get_json_attrs( + self, with_user_info=True # type: Optional[bool] + ): + # type: (...) -> Any + attrs = {} + if self.release is not None: + attrs["release"] = self.release + if self.environment is not None: + attrs["environment"] = self.environment + if with_user_info: + if self.ip_address is not None: + attrs["ip_address"] = self.ip_address + if self.user_agent is not None: + attrs["user_agent"] = self.user_agent + return attrs + + def to_json(self): + # type: (...) -> Any + rv = { + "sid": str(self.sid), + "init": True, + "started": format_timestamp(self.started), + "timestamp": format_timestamp(self.timestamp), + "status": self.status, + } # type: Dict[str, Any] + if self.errors: + rv["errors"] = self.errors + if self.did is not None: + rv["did"] = self.did + if self.duration is not None: + rv["duration"] = self.duration + attrs = self.get_json_attrs() + if attrs: + rv["attrs"] = attrs + return rv diff --git a/aws/lambda_demo/sentry_sdk/sessions.py b/aws/lambda_demo/sentry_sdk/sessions.py new file mode 100644 index 000000000..eaeb915e7 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/sessions.py @@ -0,0 +1,278 @@ +import os +import time +import warnings +from threading import Thread, Lock +from contextlib import contextmanager + +import sentry_sdk +from sentry_sdk.envelope import Envelope +from sentry_sdk.session import Session +from sentry_sdk.utils import format_timestamp + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import Dict + from typing import Generator + from typing import List + from typing import Optional + from typing import Union + + +def is_auto_session_tracking_enabled(hub=None): + # type: (Optional[sentry_sdk.Hub]) -> Union[Any, bool, None] + """DEPRECATED: Utility function to find out if session tracking is enabled.""" + + # Internal callers should use private _is_auto_session_tracking_enabled, instead. + warnings.warn( + "This function is deprecated and will be removed in the next major release. " + "There is no public API replacement.", + DeprecationWarning, + stacklevel=2, + ) + + if hub is None: + hub = sentry_sdk.Hub.current + + should_track = hub.scope._force_auto_session_tracking + + if should_track is None: + client_options = hub.client.options if hub.client else {} + should_track = client_options.get("auto_session_tracking", False) + + return should_track + + +@contextmanager +def auto_session_tracking(hub=None, session_mode="application"): + # type: (Optional[sentry_sdk.Hub], str) -> Generator[None, None, None] + """DEPRECATED: Use track_session instead + Starts and stops a session automatically around a block. + """ + warnings.warn( + "This function is deprecated and will be removed in the next major release. " + "Use track_session instead.", + DeprecationWarning, + stacklevel=2, + ) + + if hub is None: + hub = sentry_sdk.Hub.current + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + should_track = is_auto_session_tracking_enabled(hub) + if should_track: + hub.start_session(session_mode=session_mode) + try: + yield + finally: + if should_track: + hub.end_session() + + +def is_auto_session_tracking_enabled_scope(scope): + # type: (sentry_sdk.Scope) -> bool + """ + DEPRECATED: Utility function to find out if session tracking is enabled. + """ + + warnings.warn( + "This function is deprecated and will be removed in the next major release. " + "There is no public API replacement.", + DeprecationWarning, + stacklevel=2, + ) + + # Internal callers should use private _is_auto_session_tracking_enabled, instead. + return _is_auto_session_tracking_enabled(scope) + + +def _is_auto_session_tracking_enabled(scope): + # type: (sentry_sdk.Scope) -> bool + """ + Utility function to find out if session tracking is enabled. + """ + + should_track = scope._force_auto_session_tracking + if should_track is None: + client_options = sentry_sdk.get_client().options + should_track = client_options.get("auto_session_tracking", False) + + return should_track + + +@contextmanager +def auto_session_tracking_scope(scope, session_mode="application"): + # type: (sentry_sdk.Scope, str) -> Generator[None, None, None] + """DEPRECATED: This function is a deprecated alias for track_session. + Starts and stops a session automatically around a block. + """ + + warnings.warn( + "This function is a deprecated alias for track_session and will be removed in the next major release.", + DeprecationWarning, + stacklevel=2, + ) + + with track_session(scope, session_mode=session_mode): + yield + + +@contextmanager +def track_session(scope, session_mode="application"): + # type: (sentry_sdk.Scope, str) -> Generator[None, None, None] + """ + Start a new session in the provided scope, assuming session tracking is enabled. + This is a no-op context manager if session tracking is not enabled. + """ + + should_track = _is_auto_session_tracking_enabled(scope) + if should_track: + scope.start_session(session_mode=session_mode) + try: + yield + finally: + if should_track: + scope.end_session() + + +TERMINAL_SESSION_STATES = ("exited", "abnormal", "crashed") +MAX_ENVELOPE_ITEMS = 100 + + +def make_aggregate_envelope(aggregate_states, attrs): + # type: (Any, Any) -> Any + return {"attrs": dict(attrs), "aggregates": list(aggregate_states.values())} + + +class SessionFlusher: + def __init__( + self, + capture_func, # type: Callable[[Envelope], None] + flush_interval=60, # type: int + ): + # type: (...) -> None + self.capture_func = capture_func + self.flush_interval = flush_interval + self.pending_sessions = [] # type: List[Any] + self.pending_aggregates = {} # type: Dict[Any, Any] + self._thread = None # type: Optional[Thread] + self._thread_lock = Lock() + self._aggregate_lock = Lock() + self._thread_for_pid = None # type: Optional[int] + self._running = True + + def flush(self): + # type: (...) -> None + pending_sessions = self.pending_sessions + self.pending_sessions = [] + + with self._aggregate_lock: + pending_aggregates = self.pending_aggregates + self.pending_aggregates = {} + + envelope = Envelope() + for session in pending_sessions: + if len(envelope.items) == MAX_ENVELOPE_ITEMS: + self.capture_func(envelope) + envelope = Envelope() + + envelope.add_session(session) + + for attrs, states in pending_aggregates.items(): + if len(envelope.items) == MAX_ENVELOPE_ITEMS: + self.capture_func(envelope) + envelope = Envelope() + + envelope.add_sessions(make_aggregate_envelope(states, attrs)) + + if len(envelope.items) > 0: + self.capture_func(envelope) + + def _ensure_running(self): + # type: (...) -> None + """ + Check that we have an active thread to run in, or create one if not. + + Note that this might fail (e.g. in Python 3.12 it's not possible to + spawn new threads at interpreter shutdown). In that case self._running + will be False after running this function. + """ + if self._thread_for_pid == os.getpid() and self._thread is not None: + return None + with self._thread_lock: + if self._thread_for_pid == os.getpid() and self._thread is not None: + return None + + def _thread(): + # type: (...) -> None + while self._running: + time.sleep(self.flush_interval) + if self._running: + self.flush() + + thread = Thread(target=_thread) + thread.daemon = True + try: + thread.start() + except RuntimeError: + # Unfortunately at this point the interpreter is in a state that no + # longer allows us to spawn a thread and we have to bail. + self._running = False + return None + + self._thread = thread + self._thread_for_pid = os.getpid() + + return None + + def add_aggregate_session( + self, session # type: Session + ): + # type: (...) -> None + # NOTE on `session.did`: + # the protocol can deal with buckets that have a distinct-id, however + # in practice we expect the python SDK to have an extremely high cardinality + # here, effectively making aggregation useless, therefore we do not + # aggregate per-did. + + # For this part we can get away with using the global interpreter lock + with self._aggregate_lock: + attrs = session.get_json_attrs(with_user_info=False) + primary_key = tuple(sorted(attrs.items())) + secondary_key = session.truncated_started # (, session.did) + states = self.pending_aggregates.setdefault(primary_key, {}) + state = states.setdefault(secondary_key, {}) + + if "started" not in state: + state["started"] = format_timestamp(session.truncated_started) + # if session.did is not None: + # state["did"] = session.did + if session.status == "crashed": + state["crashed"] = state.get("crashed", 0) + 1 + elif session.status == "abnormal": + state["abnormal"] = state.get("abnormal", 0) + 1 + elif session.errors > 0: + state["errored"] = state.get("errored", 0) + 1 + else: + state["exited"] = state.get("exited", 0) + 1 + + def add_session( + self, session # type: Session + ): + # type: (...) -> None + if session.session_mode == "request": + self.add_aggregate_session(session) + else: + self.pending_sessions.append(session.to_json()) + self._ensure_running() + + def kill(self): + # type: (...) -> None + self._running = False + + def __del__(self): + # type: (...) -> None + self.kill() diff --git a/aws/lambda_demo/sentry_sdk/spotlight.py b/aws/lambda_demo/sentry_sdk/spotlight.py new file mode 100644 index 000000000..a783b155a --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/spotlight.py @@ -0,0 +1,232 @@ +import io +import logging +import os +import urllib.parse +import urllib.request +import urllib.error +import urllib3 +import sys + +from itertools import chain, product + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import Dict + from typing import Optional + from typing import Self + +from sentry_sdk.utils import ( + logger as sentry_logger, + env_to_bool, + capture_internal_exceptions, +) +from sentry_sdk.envelope import Envelope + + +logger = logging.getLogger("spotlight") + + +DEFAULT_SPOTLIGHT_URL = "http://localhost:8969/stream" +DJANGO_SPOTLIGHT_MIDDLEWARE_PATH = "sentry_sdk.spotlight.SpotlightMiddleware" + + +class SpotlightClient: + def __init__(self, url): + # type: (str) -> None + self.url = url + self.http = urllib3.PoolManager() + self.tries = 0 + + def capture_envelope(self, envelope): + # type: (Envelope) -> None + body = io.BytesIO() + envelope.serialize_into(body) + try: + req = self.http.request( + url=self.url, + body=body.getvalue(), + method="POST", + headers={ + "Content-Type": "application/x-sentry-envelope", + }, + ) + req.close() + except Exception as e: + # TODO: Implement buffering and retrying with exponential backoff + sentry_logger.warning(str(e)) + + +try: + from django.utils.deprecation import MiddlewareMixin + from django.http import HttpResponseServerError, HttpResponse, HttpRequest + from django.conf import settings + + SPOTLIGHT_JS_ENTRY_PATH = "/assets/main.js" + SPOTLIGHT_JS_SNIPPET_PATTERN = ( + "\n" + '\n' + ) + SPOTLIGHT_ERROR_PAGE_SNIPPET = ( + '\n' + '\n' + ) + CHARSET_PREFIX = "charset=" + BODY_TAG_NAME = "body" + BODY_CLOSE_TAG_POSSIBILITIES = tuple( + "".format("".join(chars)) + for chars in product(*zip(BODY_TAG_NAME.upper(), BODY_TAG_NAME.lower())) + ) + + class SpotlightMiddleware(MiddlewareMixin): # type: ignore[misc] + _spotlight_script = None # type: Optional[str] + + def __init__(self, get_response): + # type: (Self, Callable[..., HttpResponse]) -> None + super().__init__(get_response) + + import sentry_sdk.api + + self.sentry_sdk = sentry_sdk.api + + spotlight_client = self.sentry_sdk.get_client().spotlight + if spotlight_client is None: + sentry_logger.warning( + "Cannot find Spotlight client from SpotlightMiddleware, disabling the middleware." + ) + return None + # Spotlight URL has a trailing `/stream` part at the end so split it off + self._spotlight_url = urllib.parse.urljoin(spotlight_client.url, "../") + + @property + def spotlight_script(self): + # type: (Self) -> Optional[str] + if self._spotlight_script is None: + try: + spotlight_js_url = urllib.parse.urljoin( + self._spotlight_url, SPOTLIGHT_JS_ENTRY_PATH + ) + req = urllib.request.Request( + spotlight_js_url, + method="HEAD", + ) + urllib.request.urlopen(req) + self._spotlight_script = SPOTLIGHT_JS_SNIPPET_PATTERN.format( + spotlight_url=self._spotlight_url, + spotlight_js_url=spotlight_js_url, + ) + except urllib.error.URLError as err: + sentry_logger.debug( + "Cannot get Spotlight JS to inject at %s. SpotlightMiddleware will not be very useful.", + spotlight_js_url, + exc_info=err, + ) + + return self._spotlight_script + + def process_response(self, _request, response): + # type: (Self, HttpRequest, HttpResponse) -> Optional[HttpResponse] + content_type_header = tuple( + p.strip() + for p in response.headers.get("Content-Type", "").lower().split(";") + ) + content_type = content_type_header[0] + if len(content_type_header) > 1 and content_type_header[1].startswith( + CHARSET_PREFIX + ): + encoding = content_type_header[1][len(CHARSET_PREFIX) :] + else: + encoding = "utf-8" + + if ( + self.spotlight_script is not None + and not response.streaming + and content_type == "text/html" + ): + content_length = len(response.content) + injection = self.spotlight_script.encode(encoding) + injection_site = next( + ( + idx + for idx in ( + response.content.rfind(body_variant.encode(encoding)) + for body_variant in BODY_CLOSE_TAG_POSSIBILITIES + ) + if idx > -1 + ), + content_length, + ) + + # This approach works even when we don't have a `` tag + response.content = ( + response.content[:injection_site] + + injection + + response.content[injection_site:] + ) + + if response.has_header("Content-Length"): + response.headers["Content-Length"] = content_length + len(injection) + + return response + + def process_exception(self, _request, exception): + # type: (Self, HttpRequest, Exception) -> Optional[HttpResponseServerError] + if not settings.DEBUG: + return None + + try: + spotlight = ( + urllib.request.urlopen(self._spotlight_url).read().decode("utf-8") + ) + except urllib.error.URLError: + return None + else: + event_id = self.sentry_sdk.capture_exception(exception) + return HttpResponseServerError( + spotlight.replace( + "", + SPOTLIGHT_ERROR_PAGE_SNIPPET.format( + spotlight_url=self._spotlight_url, event_id=event_id + ), + ) + ) + +except ImportError: + settings = None + + +def setup_spotlight(options): + # type: (Dict[str, Any]) -> Optional[SpotlightClient] + _handler = logging.StreamHandler(sys.stderr) + _handler.setFormatter(logging.Formatter(" [spotlight] %(levelname)s: %(message)s")) + logger.addHandler(_handler) + logger.setLevel(logging.INFO) + + url = options.get("spotlight") + + if url is True: + url = DEFAULT_SPOTLIGHT_URL + + if not isinstance(url, str): + return None + + with capture_internal_exceptions(): + if ( + settings is not None + and settings.DEBUG + and env_to_bool(os.environ.get("SENTRY_SPOTLIGHT_ON_ERROR", "1")) + and env_to_bool(os.environ.get("SENTRY_SPOTLIGHT_MIDDLEWARE", "1")) + ): + middleware = settings.MIDDLEWARE + if DJANGO_SPOTLIGHT_MIDDLEWARE_PATH not in middleware: + settings.MIDDLEWARE = type(middleware)( + chain(middleware, (DJANGO_SPOTLIGHT_MIDDLEWARE_PATH,)) + ) + logger.info("Enabled Spotlight integration for Django") + + client = SpotlightClient(url) + logger.info("Enabled Spotlight using sidecar at %s", url) + + return client diff --git a/aws/lambda_demo/sentry_sdk/tracing.py b/aws/lambda_demo/sentry_sdk/tracing.py new file mode 100644 index 000000000..3868b2e6c --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/tracing.py @@ -0,0 +1,1322 @@ +import uuid +import random +import warnings +from datetime import datetime, timedelta, timezone + +import sentry_sdk +from sentry_sdk.consts import INSTRUMENTER, SPANSTATUS, SPANDATA +from sentry_sdk.profiler.continuous_profiler import get_profiler_id +from sentry_sdk.utils import ( + get_current_thread_meta, + is_valid_sample_rate, + logger, + nanosecond_time, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Callable, Mapping, MutableMapping + from typing import Any + from typing import Dict + from typing import Iterator + from typing import List + from typing import Optional + from typing import overload + from typing import ParamSpec + from typing import Tuple + from typing import Union + from typing import TypeVar + + from typing_extensions import TypedDict, Unpack + + P = ParamSpec("P") + R = TypeVar("R") + + import sentry_sdk.profiler + from sentry_sdk._types import ( + Event, + MeasurementUnit, + SamplingContext, + MeasurementValue, + ) + + class SpanKwargs(TypedDict, total=False): + trace_id: str + """ + The trace ID of the root span. If this new span is to be the root span, + omit this parameter, and a new trace ID will be generated. + """ + + span_id: str + """The span ID of this span. If omitted, a new span ID will be generated.""" + + parent_span_id: str + """The span ID of the parent span, if applicable.""" + + same_process_as_parent: bool + """Whether this span is in the same process as the parent span.""" + + sampled: bool + """ + Whether the span should be sampled. Overrides the default sampling decision + for this span when provided. + """ + + op: str + """ + The span's operation. A list of recommended values is available here: + https://develop.sentry.dev/sdk/performance/span-operations/ + """ + + description: str + """A description of what operation is being performed within the span. This argument is DEPRECATED. Please use the `name` parameter, instead.""" + + hub: Optional["sentry_sdk.Hub"] + """The hub to use for this span. This argument is DEPRECATED. Please use the `scope` parameter, instead.""" + + status: str + """The span's status. Possible values are listed at https://develop.sentry.dev/sdk/event-payloads/span/""" + + containing_transaction: Optional["Transaction"] + """The transaction that this span belongs to.""" + + start_timestamp: Optional[Union[datetime, float]] + """ + The timestamp when the span started. If omitted, the current time + will be used. + """ + + scope: "sentry_sdk.Scope" + """The scope to use for this span. If not provided, we use the current scope.""" + + origin: str + """ + The origin of the span. + See https://develop.sentry.dev/sdk/performance/trace-origin/ + Default "manual". + """ + + name: str + """A string describing what operation is being performed within the span/transaction.""" + + class TransactionKwargs(SpanKwargs, total=False): + source: str + """ + A string describing the source of the transaction name. This will be used to determine the transaction's type. + See https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations for more information. + Default "custom". + """ + + parent_sampled: bool + """Whether the parent transaction was sampled. If True this transaction will be kept, if False it will be discarded.""" + + baggage: "Baggage" + """The W3C baggage header value. (see https://www.w3.org/TR/baggage/)""" + + ProfileContext = TypedDict( + "ProfileContext", + { + "profiler_id": str, + }, + ) + +BAGGAGE_HEADER_NAME = "baggage" +SENTRY_TRACE_HEADER_NAME = "sentry-trace" + +# Transaction source +# see https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations +TRANSACTION_SOURCE_CUSTOM = "custom" +TRANSACTION_SOURCE_URL = "url" +TRANSACTION_SOURCE_ROUTE = "route" +TRANSACTION_SOURCE_VIEW = "view" +TRANSACTION_SOURCE_COMPONENT = "component" +TRANSACTION_SOURCE_TASK = "task" + +# These are typically high cardinality and the server hates them +LOW_QUALITY_TRANSACTION_SOURCES = [ + TRANSACTION_SOURCE_URL, +] + +SOURCE_FOR_STYLE = { + "endpoint": TRANSACTION_SOURCE_COMPONENT, + "function_name": TRANSACTION_SOURCE_COMPONENT, + "handler_name": TRANSACTION_SOURCE_COMPONENT, + "method_and_path_pattern": TRANSACTION_SOURCE_ROUTE, + "path": TRANSACTION_SOURCE_URL, + "route_name": TRANSACTION_SOURCE_COMPONENT, + "route_pattern": TRANSACTION_SOURCE_ROUTE, + "uri_template": TRANSACTION_SOURCE_ROUTE, + "url": TRANSACTION_SOURCE_ROUTE, +} + + +def get_span_status_from_http_code(http_status_code): + # type: (int) -> str + """ + Returns the Sentry status corresponding to the given HTTP status code. + + See: https://develop.sentry.dev/sdk/event-payloads/contexts/#trace-context + """ + if http_status_code < 400: + return SPANSTATUS.OK + + elif 400 <= http_status_code < 500: + if http_status_code == 403: + return SPANSTATUS.PERMISSION_DENIED + elif http_status_code == 404: + return SPANSTATUS.NOT_FOUND + elif http_status_code == 429: + return SPANSTATUS.RESOURCE_EXHAUSTED + elif http_status_code == 413: + return SPANSTATUS.FAILED_PRECONDITION + elif http_status_code == 401: + return SPANSTATUS.UNAUTHENTICATED + elif http_status_code == 409: + return SPANSTATUS.ALREADY_EXISTS + else: + return SPANSTATUS.INVALID_ARGUMENT + + elif 500 <= http_status_code < 600: + if http_status_code == 504: + return SPANSTATUS.DEADLINE_EXCEEDED + elif http_status_code == 501: + return SPANSTATUS.UNIMPLEMENTED + elif http_status_code == 503: + return SPANSTATUS.UNAVAILABLE + else: + return SPANSTATUS.INTERNAL_ERROR + + return SPANSTATUS.UNKNOWN_ERROR + + +class _SpanRecorder: + """Limits the number of spans recorded in a transaction.""" + + __slots__ = ("maxlen", "spans") + + def __init__(self, maxlen): + # type: (int) -> None + # FIXME: this is `maxlen - 1` only to preserve historical behavior + # enforced by tests. + # Either this should be changed to `maxlen` or the JS SDK implementation + # should be changed to match a consistent interpretation of what maxlen + # limits: either transaction+spans or only child spans. + self.maxlen = maxlen - 1 + self.spans = [] # type: List[Span] + + def add(self, span): + # type: (Span) -> None + if len(self.spans) > self.maxlen: + span._span_recorder = None + else: + self.spans.append(span) + + +class Span: + """A span holds timing information of a block of code. + Spans can have multiple child spans thus forming a span tree. + + :param trace_id: The trace ID of the root span. If this new span is to be the root span, + omit this parameter, and a new trace ID will be generated. + :param span_id: The span ID of this span. If omitted, a new span ID will be generated. + :param parent_span_id: The span ID of the parent span, if applicable. + :param same_process_as_parent: Whether this span is in the same process as the parent span. + :param sampled: Whether the span should be sampled. Overrides the default sampling decision + for this span when provided. + :param op: The span's operation. A list of recommended values is available here: + https://develop.sentry.dev/sdk/performance/span-operations/ + :param description: A description of what operation is being performed within the span. + + .. deprecated:: 2.15.0 + Please use the `name` parameter, instead. + :param name: A string describing what operation is being performed within the span. + :param hub: The hub to use for this span. + + .. deprecated:: 2.0.0 + Please use the `scope` parameter, instead. + :param status: The span's status. Possible values are listed at + https://develop.sentry.dev/sdk/event-payloads/span/ + :param containing_transaction: The transaction that this span belongs to. + :param start_timestamp: The timestamp when the span started. If omitted, the current time + will be used. + :param scope: The scope to use for this span. If not provided, we use the current scope. + """ + + __slots__ = ( + "trace_id", + "span_id", + "parent_span_id", + "same_process_as_parent", + "sampled", + "op", + "description", + "_measurements", + "start_timestamp", + "_start_timestamp_monotonic_ns", + "status", + "timestamp", + "_tags", + "_data", + "_span_recorder", + "hub", + "_context_manager_state", + "_containing_transaction", + "_local_aggregator", + "scope", + "origin", + "name", + ) + + def __init__( + self, + trace_id=None, # type: Optional[str] + span_id=None, # type: Optional[str] + parent_span_id=None, # type: Optional[str] + same_process_as_parent=True, # type: bool + sampled=None, # type: Optional[bool] + op=None, # type: Optional[str] + description=None, # type: Optional[str] + hub=None, # type: Optional[sentry_sdk.Hub] # deprecated + status=None, # type: Optional[str] + containing_transaction=None, # type: Optional[Transaction] + start_timestamp=None, # type: Optional[Union[datetime, float]] + scope=None, # type: Optional[sentry_sdk.Scope] + origin="manual", # type: str + name=None, # type: Optional[str] + ): + # type: (...) -> None + self.trace_id = trace_id or uuid.uuid4().hex + self.span_id = span_id or uuid.uuid4().hex[16:] + self.parent_span_id = parent_span_id + self.same_process_as_parent = same_process_as_parent + self.sampled = sampled + self.op = op + self.description = name or description + self.status = status + self.hub = hub # backwards compatibility + self.scope = scope + self.origin = origin + self._measurements = {} # type: Dict[str, MeasurementValue] + self._tags = {} # type: MutableMapping[str, str] + self._data = {} # type: Dict[str, Any] + self._containing_transaction = containing_transaction + + if hub is not None: + warnings.warn( + "The `hub` parameter is deprecated. Please use `scope` instead.", + DeprecationWarning, + stacklevel=2, + ) + + self.scope = self.scope or hub.scope + + if start_timestamp is None: + start_timestamp = datetime.now(timezone.utc) + elif isinstance(start_timestamp, float): + start_timestamp = datetime.fromtimestamp(start_timestamp, timezone.utc) + self.start_timestamp = start_timestamp + try: + # profiling depends on this value and requires that + # it is measured in nanoseconds + self._start_timestamp_monotonic_ns = nanosecond_time() + except AttributeError: + pass + + #: End timestamp of span + self.timestamp = None # type: Optional[datetime] + + self._span_recorder = None # type: Optional[_SpanRecorder] + self._local_aggregator = None # type: Optional[LocalAggregator] + + self.update_active_thread() + self.set_profiler_id(get_profiler_id()) + + # TODO this should really live on the Transaction class rather than the Span + # class + def init_span_recorder(self, maxlen): + # type: (int) -> None + if self._span_recorder is None: + self._span_recorder = _SpanRecorder(maxlen) + + def _get_local_aggregator(self): + # type: (...) -> LocalAggregator + rv = self._local_aggregator + if rv is None: + rv = self._local_aggregator = LocalAggregator() + return rv + + def __repr__(self): + # type: () -> str + return ( + "<%s(op=%r, description:%r, trace_id=%r, span_id=%r, parent_span_id=%r, sampled=%r, origin=%r)>" + % ( + self.__class__.__name__, + self.op, + self.description, + self.trace_id, + self.span_id, + self.parent_span_id, + self.sampled, + self.origin, + ) + ) + + def __enter__(self): + # type: () -> Span + scope = self.scope or sentry_sdk.get_current_scope() + old_span = scope.span + scope.span = self + self._context_manager_state = (scope, old_span) + return self + + def __exit__(self, ty, value, tb): + # type: (Optional[Any], Optional[Any], Optional[Any]) -> None + if value is not None: + self.set_status(SPANSTATUS.INTERNAL_ERROR) + + scope, old_span = self._context_manager_state + del self._context_manager_state + self.finish(scope) + scope.span = old_span + + @property + def containing_transaction(self): + # type: () -> Optional[Transaction] + """The ``Transaction`` that this span belongs to. + The ``Transaction`` is the root of the span tree, + so one could also think of this ``Transaction`` as the "root span".""" + + # this is a getter rather than a regular attribute so that transactions + # can return `self` here instead (as a way to prevent them circularly + # referencing themselves) + return self._containing_transaction + + def start_child(self, instrumenter=INSTRUMENTER.SENTRY, **kwargs): + # type: (str, **Any) -> Span + """ + Start a sub-span from the current span or transaction. + + Takes the same arguments as the initializer of :py:class:`Span`. The + trace id, sampling decision, transaction pointer, and span recorder are + inherited from the current span/transaction. + + The instrumenter parameter is deprecated for user code, and it will + be removed in the next major version. Going forward, it should only + be used by the SDK itself. + """ + if kwargs.get("description") is not None: + warnings.warn( + "The `description` parameter is deprecated. Please use `name` instead.", + DeprecationWarning, + stacklevel=2, + ) + + configuration_instrumenter = sentry_sdk.get_client().options["instrumenter"] + + if instrumenter != configuration_instrumenter: + return NoOpSpan() + + kwargs.setdefault("sampled", self.sampled) + + child = Span( + trace_id=self.trace_id, + parent_span_id=self.span_id, + containing_transaction=self.containing_transaction, + **kwargs, + ) + + span_recorder = ( + self.containing_transaction and self.containing_transaction._span_recorder + ) + if span_recorder: + span_recorder.add(child) + + return child + + @classmethod + def continue_from_environ( + cls, + environ, # type: Mapping[str, str] + **kwargs, # type: Any + ): + # type: (...) -> Transaction + """ + Create a Transaction with the given params, then add in data pulled from + the ``sentry-trace`` and ``baggage`` headers from the environ (if any) + before returning the Transaction. + + This is different from :py:meth:`~sentry_sdk.tracing.Span.continue_from_headers` + in that it assumes header names in the form ``HTTP_HEADER_NAME`` - + such as you would get from a WSGI/ASGI environ - + rather than the form ``header-name``. + + :param environ: The ASGI/WSGI environ to pull information from. + """ + if cls is Span: + logger.warning( + "Deprecated: use Transaction.continue_from_environ " + "instead of Span.continue_from_environ." + ) + return Transaction.continue_from_headers(EnvironHeaders(environ), **kwargs) + + @classmethod + def continue_from_headers( + cls, + headers, # type: Mapping[str, str] + **kwargs, # type: Any + ): + # type: (...) -> Transaction + """ + Create a transaction with the given params (including any data pulled from + the ``sentry-trace`` and ``baggage`` headers). + + :param headers: The dictionary with the HTTP headers to pull information from. + """ + # TODO move this to the Transaction class + if cls is Span: + logger.warning( + "Deprecated: use Transaction.continue_from_headers " + "instead of Span.continue_from_headers." + ) + + # TODO-neel move away from this kwargs stuff, it's confusing and opaque + # make more explicit + baggage = Baggage.from_incoming_header(headers.get(BAGGAGE_HEADER_NAME)) + kwargs.update({BAGGAGE_HEADER_NAME: baggage}) + + sentrytrace_kwargs = extract_sentrytrace_data( + headers.get(SENTRY_TRACE_HEADER_NAME) + ) + + if sentrytrace_kwargs is not None: + kwargs.update(sentrytrace_kwargs) + + # If there's an incoming sentry-trace but no incoming baggage header, + # for instance in traces coming from older SDKs, + # baggage will be empty and immutable and won't be populated as head SDK. + baggage.freeze() + + transaction = Transaction(**kwargs) + transaction.same_process_as_parent = False + + return transaction + + def iter_headers(self): + # type: () -> Iterator[Tuple[str, str]] + """ + Creates a generator which returns the span's ``sentry-trace`` and ``baggage`` headers. + If the span's containing transaction doesn't yet have a ``baggage`` value, + this will cause one to be generated and stored. + """ + if not self.containing_transaction: + # Do not propagate headers if there is no containing transaction. Otherwise, this + # span ends up being the root span of a new trace, and since it does not get sent + # to Sentry, the trace will be missing a root transaction. The dynamic sampling + # context will also be missing, breaking dynamic sampling & traces. + return + + yield SENTRY_TRACE_HEADER_NAME, self.to_traceparent() + + baggage = self.containing_transaction.get_baggage().serialize() + if baggage: + yield BAGGAGE_HEADER_NAME, baggage + + @classmethod + def from_traceparent( + cls, + traceparent, # type: Optional[str] + **kwargs, # type: Any + ): + # type: (...) -> Optional[Transaction] + """ + DEPRECATED: Use :py:meth:`sentry_sdk.tracing.Span.continue_from_headers`. + + Create a ``Transaction`` with the given params, then add in data pulled from + the given ``sentry-trace`` header value before returning the ``Transaction``. + """ + logger.warning( + "Deprecated: Use Transaction.continue_from_headers(headers, **kwargs) " + "instead of from_traceparent(traceparent, **kwargs)" + ) + + if not traceparent: + return None + + return cls.continue_from_headers( + {SENTRY_TRACE_HEADER_NAME: traceparent}, **kwargs + ) + + def to_traceparent(self): + # type: () -> str + if self.sampled is True: + sampled = "1" + elif self.sampled is False: + sampled = "0" + else: + sampled = None + + traceparent = "%s-%s" % (self.trace_id, self.span_id) + if sampled is not None: + traceparent += "-%s" % (sampled,) + + return traceparent + + def to_baggage(self): + # type: () -> Optional[Baggage] + """Returns the :py:class:`~sentry_sdk.tracing_utils.Baggage` + associated with this ``Span``, if any. (Taken from the root of the span tree.) + """ + if self.containing_transaction: + return self.containing_transaction.get_baggage() + return None + + def set_tag(self, key, value): + # type: (str, Any) -> None + self._tags[key] = value + + def set_data(self, key, value): + # type: (str, Any) -> None + self._data[key] = value + + def set_status(self, value): + # type: (str) -> None + self.status = value + + def set_measurement(self, name, value, unit=""): + # type: (str, float, MeasurementUnit) -> None + self._measurements[name] = {"value": value, "unit": unit} + + def set_thread(self, thread_id, thread_name): + # type: (Optional[int], Optional[str]) -> None + + if thread_id is not None: + self.set_data(SPANDATA.THREAD_ID, str(thread_id)) + + if thread_name is not None: + self.set_data(SPANDATA.THREAD_NAME, thread_name) + + def set_profiler_id(self, profiler_id): + # type: (Optional[str]) -> None + if profiler_id is not None: + self.set_data(SPANDATA.PROFILER_ID, profiler_id) + + def set_http_status(self, http_status): + # type: (int) -> None + self.set_tag( + "http.status_code", str(http_status) + ) # we keep this for backwards compatibility + self.set_data(SPANDATA.HTTP_STATUS_CODE, http_status) + self.set_status(get_span_status_from_http_code(http_status)) + + def is_success(self): + # type: () -> bool + return self.status == "ok" + + def finish(self, scope=None, end_timestamp=None): + # type: (Optional[sentry_sdk.Scope], Optional[Union[float, datetime]]) -> Optional[str] + """ + Sets the end timestamp of the span. + + Additionally it also creates a breadcrumb from the span, + if the span represents a database or HTTP request. + + :param scope: The scope to use for this transaction. + If not provided, the current scope will be used. + :param end_timestamp: Optional timestamp that should + be used as timestamp instead of the current time. + + :return: Always ``None``. The type is ``Optional[str]`` to match + the return value of :py:meth:`sentry_sdk.tracing.Transaction.finish`. + """ + if self.timestamp is not None: + # This span is already finished, ignore. + return None + + try: + if end_timestamp: + if isinstance(end_timestamp, float): + end_timestamp = datetime.fromtimestamp(end_timestamp, timezone.utc) + self.timestamp = end_timestamp + else: + elapsed = nanosecond_time() - self._start_timestamp_monotonic_ns + self.timestamp = self.start_timestamp + timedelta( + microseconds=elapsed / 1000 + ) + except AttributeError: + self.timestamp = datetime.now(timezone.utc) + + scope = scope or sentry_sdk.get_current_scope() + maybe_create_breadcrumbs_from_span(scope, self) + + return None + + def to_json(self): + # type: () -> Dict[str, Any] + """Returns a JSON-compatible representation of the span.""" + + rv = { + "trace_id": self.trace_id, + "span_id": self.span_id, + "parent_span_id": self.parent_span_id, + "same_process_as_parent": self.same_process_as_parent, + "op": self.op, + "description": self.description, + "start_timestamp": self.start_timestamp, + "timestamp": self.timestamp, + "origin": self.origin, + } # type: Dict[str, Any] + + if self.status: + self._tags["status"] = self.status + + if self._local_aggregator is not None: + metrics_summary = self._local_aggregator.to_json() + if metrics_summary: + rv["_metrics_summary"] = metrics_summary + + if len(self._measurements) > 0: + rv["measurements"] = self._measurements + + tags = self._tags + if tags: + rv["tags"] = tags + + data = self._data + if data: + rv["data"] = data + + return rv + + def get_trace_context(self): + # type: () -> Any + rv = { + "trace_id": self.trace_id, + "span_id": self.span_id, + "parent_span_id": self.parent_span_id, + "op": self.op, + "description": self.description, + "origin": self.origin, + } # type: Dict[str, Any] + if self.status: + rv["status"] = self.status + + if self.containing_transaction: + rv["dynamic_sampling_context"] = ( + self.containing_transaction.get_baggage().dynamic_sampling_context() + ) + + data = {} + + thread_id = self._data.get(SPANDATA.THREAD_ID) + if thread_id is not None: + data["thread.id"] = thread_id + + thread_name = self._data.get(SPANDATA.THREAD_NAME) + if thread_name is not None: + data["thread.name"] = thread_name + + if data: + rv["data"] = data + + return rv + + def get_profile_context(self): + # type: () -> Optional[ProfileContext] + profiler_id = self._data.get(SPANDATA.PROFILER_ID) + if profiler_id is None: + return None + + return { + "profiler_id": profiler_id, + } + + def update_active_thread(self): + # type: () -> None + thread_id, thread_name = get_current_thread_meta() + self.set_thread(thread_id, thread_name) + + +class Transaction(Span): + """The Transaction is the root element that holds all the spans + for Sentry performance instrumentation. + + :param name: Identifier of the transaction. + Will show up in the Sentry UI. + :param parent_sampled: Whether the parent transaction was sampled. + If True this transaction will be kept, if False it will be discarded. + :param baggage: The W3C baggage header value. + (see https://www.w3.org/TR/baggage/) + :param source: A string describing the source of the transaction name. + This will be used to determine the transaction's type. + See https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations + for more information. Default "custom". + :param kwargs: Additional arguments to be passed to the Span constructor. + See :py:class:`sentry_sdk.tracing.Span` for available arguments. + """ + + __slots__ = ( + "name", + "source", + "parent_sampled", + # used to create baggage value for head SDKs in dynamic sampling + "sample_rate", + "_measurements", + "_contexts", + "_profile", + "_baggage", + ) + + def __init__( # type: ignore[misc] + self, + name="", # type: str + parent_sampled=None, # type: Optional[bool] + baggage=None, # type: Optional[Baggage] + source=TRANSACTION_SOURCE_CUSTOM, # type: str + **kwargs, # type: Unpack[SpanKwargs] + ): + # type: (...) -> None + + super().__init__(**kwargs) + + self.name = name + self.source = source + self.sample_rate = None # type: Optional[float] + self.parent_sampled = parent_sampled + self._measurements = {} # type: Dict[str, MeasurementValue] + self._contexts = {} # type: Dict[str, Any] + self._profile = ( + None + ) # type: Optional[sentry_sdk.profiler.transaction_profiler.Profile] + self._baggage = baggage + + def __repr__(self): + # type: () -> str + return ( + "<%s(name=%r, op=%r, trace_id=%r, span_id=%r, parent_span_id=%r, sampled=%r, source=%r, origin=%r)>" + % ( + self.__class__.__name__, + self.name, + self.op, + self.trace_id, + self.span_id, + self.parent_span_id, + self.sampled, + self.source, + self.origin, + ) + ) + + def _possibly_started(self): + # type: () -> bool + """Returns whether the transaction might have been started. + + If this returns False, we know that the transaction was not started + with sentry_sdk.start_transaction, and therefore the transaction will + be discarded. + """ + + # We must explicitly check self.sampled is False since self.sampled can be None + return self._span_recorder is not None or self.sampled is False + + def __enter__(self): + # type: () -> Transaction + if not self._possibly_started(): + logger.debug( + "Transaction was entered without being started with sentry_sdk.start_transaction." + "The transaction will not be sent to Sentry. To fix, start the transaction by" + "passing it to sentry_sdk.start_transaction." + ) + + super().__enter__() + + if self._profile is not None: + self._profile.__enter__() + + return self + + def __exit__(self, ty, value, tb): + # type: (Optional[Any], Optional[Any], Optional[Any]) -> None + if self._profile is not None: + self._profile.__exit__(ty, value, tb) + + super().__exit__(ty, value, tb) + + @property + def containing_transaction(self): + # type: () -> Transaction + """The root element of the span tree. + In the case of a transaction it is the transaction itself. + """ + + # Transactions (as spans) belong to themselves (as transactions). This + # is a getter rather than a regular attribute to avoid having a circular + # reference. + return self + + def _get_scope_from_finish_args( + self, + scope_arg, # type: Optional[Union[sentry_sdk.Scope, sentry_sdk.Hub]] + hub_arg, # type: Optional[Union[sentry_sdk.Scope, sentry_sdk.Hub]] + ): + # type: (...) -> Optional[sentry_sdk.Scope] + """ + Logic to get the scope from the arguments passed to finish. This + function exists for backwards compatibility with the old finish. + + TODO: Remove this function in the next major version. + """ + scope_or_hub = scope_arg + if hub_arg is not None: + warnings.warn( + "The `hub` parameter is deprecated. Please use the `scope` parameter, instead.", + DeprecationWarning, + stacklevel=3, + ) + + scope_or_hub = hub_arg + + if isinstance(scope_or_hub, sentry_sdk.Hub): + warnings.warn( + "Passing a Hub to finish is deprecated. Please pass a Scope, instead.", + DeprecationWarning, + stacklevel=3, + ) + + return scope_or_hub.scope + + return scope_or_hub + + def finish( + self, + scope=None, # type: Optional[sentry_sdk.Scope] + end_timestamp=None, # type: Optional[Union[float, datetime]] + *, + hub=None, # type: Optional[sentry_sdk.Hub] + ): + # type: (...) -> Optional[str] + """Finishes the transaction and sends it to Sentry. + All finished spans in the transaction will also be sent to Sentry. + + :param scope: The Scope to use for this transaction. + If not provided, the current Scope will be used. + :param end_timestamp: Optional timestamp that should + be used as timestamp instead of the current time. + :param hub: The hub to use for this transaction. + This argument is DEPRECATED. Please use the `scope` + parameter, instead. + + :return: The event ID if the transaction was sent to Sentry, + otherwise None. + """ + if self.timestamp is not None: + # This transaction is already finished, ignore. + return None + + # For backwards compatibility, we must handle the case where `scope` + # or `hub` could both either be a `Scope` or a `Hub`. + scope = self._get_scope_from_finish_args( + scope, hub + ) # type: Optional[sentry_sdk.Scope] + + scope = scope or self.scope or sentry_sdk.get_current_scope() + client = sentry_sdk.get_client() + + if not client.is_active(): + # We have no active client and therefore nowhere to send this transaction. + return None + + if self._span_recorder is None: + # Explicit check against False needed because self.sampled might be None + if self.sampled is False: + logger.debug("Discarding transaction because sampled = False") + else: + logger.debug( + "Discarding transaction because it was not started with sentry_sdk.start_transaction" + ) + + # This is not entirely accurate because discards here are not + # exclusively based on sample rate but also traces sampler, but + # we handle this the same here. + if client.transport and has_tracing_enabled(client.options): + if client.monitor and client.monitor.downsample_factor > 0: + reason = "backpressure" + else: + reason = "sample_rate" + + client.transport.record_lost_event(reason, data_category="transaction") + + # Only one span (the transaction itself) is discarded, since we did not record any spans here. + client.transport.record_lost_event(reason, data_category="span") + return None + + if not self.name: + logger.warning( + "Transaction has no name, falling back to ``." + ) + self.name = "" + + super().finish(scope, end_timestamp) + + if not self.sampled: + # At this point a `sampled = None` should have already been resolved + # to a concrete decision. + if self.sampled is None: + logger.warning("Discarding transaction without sampling decision.") + + return None + + finished_spans = [ + span.to_json() + for span in self._span_recorder.spans + if span.timestamp is not None + ] + + # we do this to break the circular reference of transaction -> span + # recorder -> span -> containing transaction (which is where we started) + # before either the spans or the transaction goes out of scope and has + # to be garbage collected + self._span_recorder = None + + contexts = {} + contexts.update(self._contexts) + contexts.update({"trace": self.get_trace_context()}) + profile_context = self.get_profile_context() + if profile_context is not None: + contexts.update({"profile": profile_context}) + + event = { + "type": "transaction", + "transaction": self.name, + "transaction_info": {"source": self.source}, + "contexts": contexts, + "tags": self._tags, + "timestamp": self.timestamp, + "start_timestamp": self.start_timestamp, + "spans": finished_spans, + } # type: Event + + if self._profile is not None and self._profile.valid(): + event["profile"] = self._profile + self._profile = None + + event["measurements"] = self._measurements + + # This is here since `to_json` is not invoked. This really should + # be gone when we switch to onlyspans. + if self._local_aggregator is not None: + metrics_summary = self._local_aggregator.to_json() + if metrics_summary: + event["_metrics_summary"] = metrics_summary + + return scope.capture_event(event) + + def set_measurement(self, name, value, unit=""): + # type: (str, float, MeasurementUnit) -> None + self._measurements[name] = {"value": value, "unit": unit} + + def set_context(self, key, value): + # type: (str, Any) -> None + """Sets a context. Transactions can have multiple contexts + and they should follow the format described in the "Contexts Interface" + documentation. + + :param key: The name of the context. + :param value: The information about the context. + """ + self._contexts[key] = value + + def set_http_status(self, http_status): + # type: (int) -> None + """Sets the status of the Transaction according to the given HTTP status. + + :param http_status: The HTTP status code.""" + super().set_http_status(http_status) + self.set_context("response", {"status_code": http_status}) + + def to_json(self): + # type: () -> Dict[str, Any] + """Returns a JSON-compatible representation of the transaction.""" + rv = super().to_json() + + rv["name"] = self.name + rv["source"] = self.source + rv["sampled"] = self.sampled + + return rv + + def get_trace_context(self): + # type: () -> Any + trace_context = super().get_trace_context() + + if self._data: + trace_context["data"] = self._data + + return trace_context + + def get_baggage(self): + # type: () -> Baggage + """Returns the :py:class:`~sentry_sdk.tracing_utils.Baggage` + associated with the Transaction. + + The first time a new baggage with Sentry items is made, + it will be frozen.""" + + if not self._baggage or self._baggage.mutable: + self._baggage = Baggage.populate_from_transaction(self) + + return self._baggage + + def _set_initial_sampling_decision(self, sampling_context): + # type: (SamplingContext) -> None + """ + Sets the transaction's sampling decision, according to the following + precedence rules: + + 1. If a sampling decision is passed to `start_transaction` + (`start_transaction(name: "my transaction", sampled: True)`), that + decision will be used, regardless of anything else + + 2. If `traces_sampler` is defined, its decision will be used. It can + choose to keep or ignore any parent sampling decision, or use the + sampling context data to make its own decision or to choose a sample + rate for the transaction. + + 3. If `traces_sampler` is not defined, but there's a parent sampling + decision, the parent sampling decision will be used. + + 4. If `traces_sampler` is not defined and there's no parent sampling + decision, `traces_sample_rate` will be used. + """ + client = sentry_sdk.get_client() + + transaction_description = "{op}transaction <{name}>".format( + op=("<" + self.op + "> " if self.op else ""), name=self.name + ) + + # nothing to do if tracing is disabled + if not has_tracing_enabled(client.options): + self.sampled = False + return + + # if the user has forced a sampling decision by passing a `sampled` + # value when starting the transaction, go with that + if self.sampled is not None: + self.sample_rate = float(self.sampled) + return + + # we would have bailed already if neither `traces_sampler` nor + # `traces_sample_rate` were defined, so one of these should work; prefer + # the hook if so + sample_rate = ( + client.options["traces_sampler"](sampling_context) + if callable(client.options.get("traces_sampler")) + else ( + # default inheritance behavior + sampling_context["parent_sampled"] + if sampling_context["parent_sampled"] is not None + else client.options["traces_sample_rate"] + ) + ) + + # Since this is coming from the user (or from a function provided by the + # user), who knows what we might get. (The only valid values are + # booleans or numbers between 0 and 1.) + if not is_valid_sample_rate(sample_rate, source="Tracing"): + logger.warning( + "[Tracing] Discarding {transaction_description} because of invalid sample rate.".format( + transaction_description=transaction_description, + ) + ) + self.sampled = False + return + + self.sample_rate = float(sample_rate) + + if client.monitor: + self.sample_rate /= 2**client.monitor.downsample_factor + + # if the function returned 0 (or false), or if `traces_sample_rate` is + # 0, it's a sign the transaction should be dropped + if not self.sample_rate: + logger.debug( + "[Tracing] Discarding {transaction_description} because {reason}".format( + transaction_description=transaction_description, + reason=( + "traces_sampler returned 0 or False" + if callable(client.options.get("traces_sampler")) + else "traces_sample_rate is set to 0" + ), + ) + ) + self.sampled = False + return + + # Now we roll the dice. random.random is inclusive of 0, but not of 1, + # so strict < is safe here. In case sample_rate is a boolean, cast it + # to a float (True becomes 1.0 and False becomes 0.0) + self.sampled = random.random() < self.sample_rate + + if self.sampled: + logger.debug( + "[Tracing] Starting {transaction_description}".format( + transaction_description=transaction_description, + ) + ) + else: + logger.debug( + "[Tracing] Discarding {transaction_description} because it's not included in the random sample (sampling rate = {sample_rate})".format( + transaction_description=transaction_description, + sample_rate=self.sample_rate, + ) + ) + + +class NoOpSpan(Span): + def __repr__(self): + # type: () -> str + return "<%s>" % self.__class__.__name__ + + @property + def containing_transaction(self): + # type: () -> Optional[Transaction] + return None + + def start_child(self, instrumenter=INSTRUMENTER.SENTRY, **kwargs): + # type: (str, **Any) -> NoOpSpan + return NoOpSpan() + + def to_traceparent(self): + # type: () -> str + return "" + + def to_baggage(self): + # type: () -> Optional[Baggage] + return None + + def get_baggage(self): + # type: () -> Optional[Baggage] + return None + + def iter_headers(self): + # type: () -> Iterator[Tuple[str, str]] + return iter(()) + + def set_tag(self, key, value): + # type: (str, Any) -> None + pass + + def set_data(self, key, value): + # type: (str, Any) -> None + pass + + def set_status(self, value): + # type: (str) -> None + pass + + def set_http_status(self, http_status): + # type: (int) -> None + pass + + def is_success(self): + # type: () -> bool + return True + + def to_json(self): + # type: () -> Dict[str, Any] + return {} + + def get_trace_context(self): + # type: () -> Any + return {} + + def get_profile_context(self): + # type: () -> Any + return {} + + def finish( + self, + scope=None, # type: Optional[sentry_sdk.Scope] + end_timestamp=None, # type: Optional[Union[float, datetime]] + *, + hub=None, # type: Optional[sentry_sdk.Hub] + ): + # type: (...) -> Optional[str] + """ + The `hub` parameter is deprecated. Please use the `scope` parameter, instead. + """ + pass + + def set_measurement(self, name, value, unit=""): + # type: (str, float, MeasurementUnit) -> None + pass + + def set_context(self, key, value): + # type: (str, Any) -> None + pass + + def init_span_recorder(self, maxlen): + # type: (int) -> None + pass + + def _set_initial_sampling_decision(self, sampling_context): + # type: (SamplingContext) -> None + pass + + +if TYPE_CHECKING: + + @overload + def trace(func=None): + # type: (None) -> Callable[[Callable[P, R]], Callable[P, R]] + pass + + @overload + def trace(func): + # type: (Callable[P, R]) -> Callable[P, R] + pass + + +def trace(func=None): + # type: (Optional[Callable[P, R]]) -> Union[Callable[P, R], Callable[[Callable[P, R]], Callable[P, R]]] + """ + Decorator to start a child span under the existing current transaction. + If there is no current transaction, then nothing will be traced. + + .. code-block:: + :caption: Usage + + import sentry_sdk + + @sentry_sdk.trace + def my_function(): + ... + + @sentry_sdk.trace + async def my_async_function(): + ... + """ + from sentry_sdk.tracing_utils import start_child_span_decorator + + # This patterns allows usage of both @sentry_traced and @sentry_traced(...) + # See https://stackoverflow.com/questions/52126071/decorator-with-arguments-avoid-parenthesis-when-no-arguments/52126278 + if func: + return start_child_span_decorator(func) + else: + return start_child_span_decorator + + +# Circular imports + +from sentry_sdk.tracing_utils import ( + Baggage, + EnvironHeaders, + extract_sentrytrace_data, + has_tracing_enabled, + maybe_create_breadcrumbs_from_span, +) + +with warnings.catch_warnings(): + # The code in this file which uses `LocalAggregator` is only called from the deprecated `metrics` module. + warnings.simplefilter("ignore", DeprecationWarning) + from sentry_sdk.metrics import LocalAggregator diff --git a/aws/lambda_demo/sentry_sdk/tracing_utils.py b/aws/lambda_demo/sentry_sdk/tracing_utils.py new file mode 100644 index 000000000..045956377 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/tracing_utils.py @@ -0,0 +1,741 @@ +import contextlib +import inspect +import os +import re +import sys +from collections.abc import Mapping +from datetime import timedelta +from functools import wraps +from urllib.parse import quote, unquote +import uuid + +import sentry_sdk +from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.utils import ( + capture_internal_exceptions, + filename_for_module, + Dsn, + logger, + match_regex_list, + qualname_from_function, + to_string, + is_sentry_url, + _is_external_source, + _is_in_project_root, + _module_in_list, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Dict + from typing import Generator + from typing import Optional + from typing import Union + + from types import FrameType + + +SENTRY_TRACE_REGEX = re.compile( + "^[ \t]*" # whitespace + "([0-9a-f]{32})?" # trace_id + "-?([0-9a-f]{16})?" # span_id + "-?([01])?" # sampled + "[ \t]*$" # whitespace +) + +# This is a normal base64 regex, modified to reflect that fact that we strip the +# trailing = or == off +base64_stripped = ( + # any of the characters in the base64 "alphabet", in multiples of 4 + "([a-zA-Z0-9+/]{4})*" + # either nothing or 2 or 3 base64-alphabet characters (see + # https://en.wikipedia.org/wiki/Base64#Decoding_Base64_without_padding for + # why there's never only 1 extra character) + "([a-zA-Z0-9+/]{2,3})?" +) + + +class EnvironHeaders(Mapping): # type: ignore + def __init__( + self, + environ, # type: Mapping[str, str] + prefix="HTTP_", # type: str + ): + # type: (...) -> None + self.environ = environ + self.prefix = prefix + + def __getitem__(self, key): + # type: (str) -> Optional[Any] + return self.environ[self.prefix + key.replace("-", "_").upper()] + + def __len__(self): + # type: () -> int + return sum(1 for _ in iter(self)) + + def __iter__(self): + # type: () -> Generator[str, None, None] + for k in self.environ: + if not isinstance(k, str): + continue + + k = k.replace("-", "_").upper() + if not k.startswith(self.prefix): + continue + + yield k[len(self.prefix) :] + + +def has_tracing_enabled(options): + # type: (Optional[Dict[str, Any]]) -> bool + """ + Returns True if either traces_sample_rate or traces_sampler is + defined and enable_tracing is set and not false. + """ + if options is None: + return False + + return bool( + options.get("enable_tracing") is not False + and ( + options.get("traces_sample_rate") is not None + or options.get("traces_sampler") is not None + ) + ) + + +@contextlib.contextmanager +def record_sql_queries( + cursor, # type: Any + query, # type: Any + params_list, # type: Any + paramstyle, # type: Optional[str] + executemany, # type: bool + record_cursor_repr=False, # type: bool + span_origin="manual", # type: str +): + # type: (...) -> Generator[sentry_sdk.tracing.Span, None, None] + + # TODO: Bring back capturing of params by default + if sentry_sdk.get_client().options["_experiments"].get("record_sql_params", False): + if not params_list or params_list == [None]: + params_list = None + + if paramstyle == "pyformat": + paramstyle = "format" + else: + params_list = None + paramstyle = None + + query = _format_sql(cursor, query) + + data = {} + if params_list is not None: + data["db.params"] = params_list + if paramstyle is not None: + data["db.paramstyle"] = paramstyle + if executemany: + data["db.executemany"] = True + if record_cursor_repr and cursor is not None: + data["db.cursor"] = cursor + + with capture_internal_exceptions(): + sentry_sdk.add_breadcrumb(message=query, category="query", data=data) + + with sentry_sdk.start_span( + op=OP.DB, + name=query, + origin=span_origin, + ) as span: + for k, v in data.items(): + span.set_data(k, v) + yield span + + +def maybe_create_breadcrumbs_from_span(scope, span): + # type: (sentry_sdk.Scope, sentry_sdk.tracing.Span) -> None + + if span.op == OP.DB_REDIS: + scope.add_breadcrumb( + message=span.description, type="redis", category="redis", data=span._tags + ) + elif span.op == OP.HTTP_CLIENT: + scope.add_breadcrumb(type="http", category="httplib", data=span._data) + elif span.op == "subprocess": + scope.add_breadcrumb( + type="subprocess", + category="subprocess", + message=span.description, + data=span._data, + ) + + +def _get_frame_module_abs_path(frame): + # type: (FrameType) -> Optional[str] + try: + return frame.f_code.co_filename + except Exception: + return None + + +def _should_be_included( + is_sentry_sdk_frame, # type: bool + namespace, # type: Optional[str] + in_app_include, # type: Optional[list[str]] + in_app_exclude, # type: Optional[list[str]] + abs_path, # type: Optional[str] + project_root, # type: Optional[str] +): + # type: (...) -> bool + # in_app_include takes precedence over in_app_exclude + should_be_included = _module_in_list(namespace, in_app_include) + should_be_excluded = _is_external_source(abs_path) or _module_in_list( + namespace, in_app_exclude + ) + return not is_sentry_sdk_frame and ( + should_be_included + or (_is_in_project_root(abs_path, project_root) and not should_be_excluded) + ) + + +def add_query_source(span): + # type: (sentry_sdk.tracing.Span) -> None + """ + Adds OTel compatible source code information to the span + """ + client = sentry_sdk.get_client() + if not client.is_active(): + return + + if span.timestamp is None or span.start_timestamp is None: + return + + should_add_query_source = client.options.get("enable_db_query_source", True) + if not should_add_query_source: + return + + duration = span.timestamp - span.start_timestamp + threshold = client.options.get("db_query_source_threshold_ms", 0) + slow_query = duration / timedelta(milliseconds=1) > threshold + + if not slow_query: + return + + project_root = client.options["project_root"] + in_app_include = client.options.get("in_app_include") + in_app_exclude = client.options.get("in_app_exclude") + + # Find the correct frame + frame = sys._getframe() # type: Union[FrameType, None] + while frame is not None: + abs_path = _get_frame_module_abs_path(frame) + + try: + namespace = frame.f_globals.get("__name__") # type: Optional[str] + except Exception: + namespace = None + + is_sentry_sdk_frame = namespace is not None and namespace.startswith( + "sentry_sdk." + ) + + should_be_included = _should_be_included( + is_sentry_sdk_frame=is_sentry_sdk_frame, + namespace=namespace, + in_app_include=in_app_include, + in_app_exclude=in_app_exclude, + abs_path=abs_path, + project_root=project_root, + ) + if should_be_included: + break + + frame = frame.f_back + else: + frame = None + + # Set the data + if frame is not None: + try: + lineno = frame.f_lineno + except Exception: + lineno = None + if lineno is not None: + span.set_data(SPANDATA.CODE_LINENO, frame.f_lineno) + + try: + namespace = frame.f_globals.get("__name__") + except Exception: + namespace = None + if namespace is not None: + span.set_data(SPANDATA.CODE_NAMESPACE, namespace) + + filepath = _get_frame_module_abs_path(frame) + if filepath is not None: + if namespace is not None: + in_app_path = filename_for_module(namespace, filepath) + elif project_root is not None and filepath.startswith(project_root): + in_app_path = filepath.replace(project_root, "").lstrip(os.sep) + else: + in_app_path = filepath + span.set_data(SPANDATA.CODE_FILEPATH, in_app_path) + + try: + code_function = frame.f_code.co_name + except Exception: + code_function = None + + if code_function is not None: + span.set_data(SPANDATA.CODE_FUNCTION, frame.f_code.co_name) + + +def extract_sentrytrace_data(header): + # type: (Optional[str]) -> Optional[Dict[str, Union[str, bool, None]]] + """ + Given a `sentry-trace` header string, return a dictionary of data. + """ + if not header: + return None + + if header.startswith("00-") and header.endswith("-00"): + header = header[3:-3] + + match = SENTRY_TRACE_REGEX.match(header) + if not match: + return None + + trace_id, parent_span_id, sampled_str = match.groups() + parent_sampled = None + + if trace_id: + trace_id = "{:032x}".format(int(trace_id, 16)) + if parent_span_id: + parent_span_id = "{:016x}".format(int(parent_span_id, 16)) + if sampled_str: + parent_sampled = sampled_str != "0" + + return { + "trace_id": trace_id, + "parent_span_id": parent_span_id, + "parent_sampled": parent_sampled, + } + + +def _format_sql(cursor, sql): + # type: (Any, str) -> Optional[str] + + real_sql = None + + # If we're using psycopg2, it could be that we're + # looking at a query that uses Composed objects. Use psycopg2's mogrify + # function to format the query. We lose per-parameter trimming but gain + # accuracy in formatting. + try: + if hasattr(cursor, "mogrify"): + real_sql = cursor.mogrify(sql) + if isinstance(real_sql, bytes): + real_sql = real_sql.decode(cursor.connection.encoding) + except Exception: + real_sql = None + + return real_sql or to_string(sql) + + +class PropagationContext: + """ + The PropagationContext represents the data of a trace in Sentry. + """ + + __slots__ = ( + "_trace_id", + "_span_id", + "parent_span_id", + "parent_sampled", + "dynamic_sampling_context", + ) + + def __init__( + self, + trace_id=None, # type: Optional[str] + span_id=None, # type: Optional[str] + parent_span_id=None, # type: Optional[str] + parent_sampled=None, # type: Optional[bool] + dynamic_sampling_context=None, # type: Optional[Dict[str, str]] + ): + # type: (...) -> None + self._trace_id = trace_id + """The trace id of the Sentry trace.""" + + self._span_id = span_id + """The span id of the currently executing span.""" + + self.parent_span_id = parent_span_id + """The id of the parent span that started this span. + The parent span could also be a span in an upstream service.""" + + self.parent_sampled = parent_sampled + """Boolean indicator if the parent span was sampled. + Important when the parent span originated in an upstream service, + because we watn to sample the whole trace, or nothing from the trace.""" + + self.dynamic_sampling_context = dynamic_sampling_context + """Data that is used for dynamic sampling decisions.""" + + @classmethod + def from_incoming_data(cls, incoming_data): + # type: (Dict[str, Any]) -> Optional[PropagationContext] + propagation_context = None + + normalized_data = normalize_incoming_data(incoming_data) + baggage_header = normalized_data.get(BAGGAGE_HEADER_NAME) + if baggage_header: + propagation_context = PropagationContext() + propagation_context.dynamic_sampling_context = Baggage.from_incoming_header( + baggage_header + ).dynamic_sampling_context() + + sentry_trace_header = normalized_data.get(SENTRY_TRACE_HEADER_NAME) + if sentry_trace_header: + sentrytrace_data = extract_sentrytrace_data(sentry_trace_header) + if sentrytrace_data is not None: + if propagation_context is None: + propagation_context = PropagationContext() + propagation_context.update(sentrytrace_data) + + return propagation_context + + @property + def trace_id(self): + # type: () -> str + """The trace id of the Sentry trace.""" + if not self._trace_id: + self._trace_id = uuid.uuid4().hex + + return self._trace_id + + @trace_id.setter + def trace_id(self, value): + # type: (str) -> None + self._trace_id = value + + @property + def span_id(self): + # type: () -> str + """The span id of the currently executed span.""" + if not self._span_id: + self._span_id = uuid.uuid4().hex[16:] + + return self._span_id + + @span_id.setter + def span_id(self, value): + # type: (str) -> None + self._span_id = value + + def update(self, other_dict): + # type: (Dict[str, Any]) -> None + """ + Updates the PropagationContext with data from the given dictionary. + """ + for key, value in other_dict.items(): + try: + setattr(self, key, value) + except AttributeError: + pass + + def __repr__(self): + # type: (...) -> str + return "".format( + self._trace_id, + self._span_id, + self.parent_span_id, + self.parent_sampled, + self.dynamic_sampling_context, + ) + + +class Baggage: + """ + The W3C Baggage header information (see https://www.w3.org/TR/baggage/). + """ + + __slots__ = ("sentry_items", "third_party_items", "mutable") + + SENTRY_PREFIX = "sentry-" + SENTRY_PREFIX_REGEX = re.compile("^sentry-") + + def __init__( + self, + sentry_items, # type: Dict[str, str] + third_party_items="", # type: str + mutable=True, # type: bool + ): + self.sentry_items = sentry_items + self.third_party_items = third_party_items + self.mutable = mutable + + @classmethod + def from_incoming_header(cls, header): + # type: (Optional[str]) -> Baggage + """ + freeze if incoming header already has sentry baggage + """ + sentry_items = {} + third_party_items = "" + mutable = True + + if header: + for item in header.split(","): + if "=" not in item: + continue + + with capture_internal_exceptions(): + item = item.strip() + key, val = item.split("=") + if Baggage.SENTRY_PREFIX_REGEX.match(key): + baggage_key = unquote(key.split("-")[1]) + sentry_items[baggage_key] = unquote(val) + mutable = False + else: + third_party_items += ("," if third_party_items else "") + item + + return Baggage(sentry_items, third_party_items, mutable) + + @classmethod + def from_options(cls, scope): + # type: (sentry_sdk.scope.Scope) -> Optional[Baggage] + + sentry_items = {} # type: Dict[str, str] + third_party_items = "" + mutable = False + + client = sentry_sdk.get_client() + + if not client.is_active() or scope._propagation_context is None: + return Baggage(sentry_items) + + options = client.options + propagation_context = scope._propagation_context + + if propagation_context is not None: + sentry_items["trace_id"] = propagation_context.trace_id + + if options.get("environment"): + sentry_items["environment"] = options["environment"] + + if options.get("release"): + sentry_items["release"] = options["release"] + + if options.get("dsn"): + sentry_items["public_key"] = Dsn(options["dsn"]).public_key + + if options.get("traces_sample_rate"): + sentry_items["sample_rate"] = str(options["traces_sample_rate"]) + + return Baggage(sentry_items, third_party_items, mutable) + + @classmethod + def populate_from_transaction(cls, transaction): + # type: (sentry_sdk.tracing.Transaction) -> Baggage + """ + Populate fresh baggage entry with sentry_items and make it immutable + if this is the head SDK which originates traces. + """ + client = sentry_sdk.get_client() + sentry_items = {} # type: Dict[str, str] + + if not client.is_active(): + return Baggage(sentry_items) + + options = client.options or {} + + sentry_items["trace_id"] = transaction.trace_id + + if options.get("environment"): + sentry_items["environment"] = options["environment"] + + if options.get("release"): + sentry_items["release"] = options["release"] + + if options.get("dsn"): + sentry_items["public_key"] = Dsn(options["dsn"]).public_key + + if ( + transaction.name + and transaction.source not in LOW_QUALITY_TRANSACTION_SOURCES + ): + sentry_items["transaction"] = transaction.name + + if transaction.sample_rate is not None: + sentry_items["sample_rate"] = str(transaction.sample_rate) + + if transaction.sampled is not None: + sentry_items["sampled"] = "true" if transaction.sampled else "false" + + # there's an existing baggage but it was mutable, + # which is why we are creating this new baggage. + # However, if by chance the user put some sentry items in there, give them precedence. + if transaction._baggage and transaction._baggage.sentry_items: + sentry_items.update(transaction._baggage.sentry_items) + + return Baggage(sentry_items, mutable=False) + + def freeze(self): + # type: () -> None + self.mutable = False + + def dynamic_sampling_context(self): + # type: () -> Dict[str, str] + header = {} + + for key, item in self.sentry_items.items(): + header[key] = item + + return header + + def serialize(self, include_third_party=False): + # type: (bool) -> str + items = [] + + for key, val in self.sentry_items.items(): + with capture_internal_exceptions(): + item = Baggage.SENTRY_PREFIX + quote(key) + "=" + quote(str(val)) + items.append(item) + + if include_third_party: + items.append(self.third_party_items) + + return ",".join(items) + + @staticmethod + def strip_sentry_baggage(header): + # type: (str) -> str + """Remove Sentry baggage from the given header. + + Given a Baggage header, return a new Baggage header with all Sentry baggage items removed. + """ + return ",".join( + ( + item + for item in header.split(",") + if not Baggage.SENTRY_PREFIX_REGEX.match(item.strip()) + ) + ) + + +def should_propagate_trace(client, url): + # type: (sentry_sdk.client.BaseClient, str) -> bool + """ + Returns True if url matches trace_propagation_targets configured in the given client. Otherwise, returns False. + """ + trace_propagation_targets = client.options["trace_propagation_targets"] + + if is_sentry_url(client, url): + return False + + return match_regex_list(url, trace_propagation_targets, substring_matching=True) + + +def normalize_incoming_data(incoming_data): + # type: (Dict[str, Any]) -> Dict[str, Any] + """ + Normalizes incoming data so the keys are all lowercase with dashes instead of underscores and stripped from known prefixes. + """ + data = {} + for key, value in incoming_data.items(): + if key.startswith("HTTP_"): + key = key[5:] + + key = key.replace("_", "-").lower() + data[key] = value + + return data + + +def start_child_span_decorator(func): + # type: (Any) -> Any + """ + Decorator to add child spans for functions. + + See also ``sentry_sdk.tracing.trace()``. + """ + # Asynchronous case + if inspect.iscoroutinefunction(func): + + @wraps(func) + async def func_with_tracing(*args, **kwargs): + # type: (*Any, **Any) -> Any + + span = get_current_span() + + if span is None: + logger.debug( + "Cannot create a child span for %s. " + "Please start a Sentry transaction before calling this function.", + qualname_from_function(func), + ) + return await func(*args, **kwargs) + + with span.start_child( + op=OP.FUNCTION, + name=qualname_from_function(func), + ): + return await func(*args, **kwargs) + + try: + func_with_tracing.__signature__ = inspect.signature(func) # type: ignore[attr-defined] + except Exception: + pass + + # Synchronous case + else: + + @wraps(func) + def func_with_tracing(*args, **kwargs): + # type: (*Any, **Any) -> Any + + span = get_current_span() + + if span is None: + logger.debug( + "Cannot create a child span for %s. " + "Please start a Sentry transaction before calling this function.", + qualname_from_function(func), + ) + return func(*args, **kwargs) + + with span.start_child( + op=OP.FUNCTION, + name=qualname_from_function(func), + ): + return func(*args, **kwargs) + + try: + func_with_tracing.__signature__ = inspect.signature(func) # type: ignore[attr-defined] + except Exception: + pass + + return func_with_tracing + + +def get_current_span(scope=None): + # type: (Optional[sentry_sdk.Scope]) -> Optional[Span] + """ + Returns the currently active span if there is one running, otherwise `None` + """ + scope = scope or sentry_sdk.get_current_scope() + current_span = scope.span + return current_span + + +# Circular imports +from sentry_sdk.tracing import ( + BAGGAGE_HEADER_NAME, + LOW_QUALITY_TRANSACTION_SOURCES, + SENTRY_TRACE_HEADER_NAME, +) + +if TYPE_CHECKING: + from sentry_sdk.tracing import Span diff --git a/aws/lambda_demo/sentry_sdk/transport.py b/aws/lambda_demo/sentry_sdk/transport.py new file mode 100644 index 000000000..879811589 --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/transport.py @@ -0,0 +1,910 @@ +from abc import ABC, abstractmethod +import io +import os +import gzip +import socket +import ssl +import time +import warnings +from datetime import datetime, timedelta, timezone +from collections import defaultdict +from urllib.request import getproxies + +try: + import brotli # type: ignore +except ImportError: + brotli = None + +import urllib3 +import certifi + +import sentry_sdk +from sentry_sdk.consts import EndpointType +from sentry_sdk.utils import Dsn, logger, capture_internal_exceptions +from sentry_sdk.worker import BackgroundWorker +from sentry_sdk.envelope import Envelope, Item, PayloadRef + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any + from typing import Callable + from typing import Dict + from typing import DefaultDict + from typing import Iterable + from typing import List + from typing import Mapping + from typing import Optional + from typing import Self + from typing import Tuple + from typing import Type + from typing import Union + + from urllib3.poolmanager import PoolManager + from urllib3.poolmanager import ProxyManager + + from sentry_sdk._types import Event, EventDataCategory + +KEEP_ALIVE_SOCKET_OPTIONS = [] +for option in [ + (socket.SOL_SOCKET, lambda: getattr(socket, "SO_KEEPALIVE"), 1), # noqa: B009 + (socket.SOL_TCP, lambda: getattr(socket, "TCP_KEEPIDLE"), 45), # noqa: B009 + (socket.SOL_TCP, lambda: getattr(socket, "TCP_KEEPINTVL"), 10), # noqa: B009 + (socket.SOL_TCP, lambda: getattr(socket, "TCP_KEEPCNT"), 6), # noqa: B009 +]: + try: + KEEP_ALIVE_SOCKET_OPTIONS.append((option[0], option[1](), option[2])) + except AttributeError: + # a specific option might not be available on specific systems, + # e.g. TCP_KEEPIDLE doesn't exist on macOS + pass + + +class Transport(ABC): + """Baseclass for all transports. + + A transport is used to send an event to sentry. + """ + + parsed_dsn = None # type: Optional[Dsn] + + def __init__(self, options=None): + # type: (Self, Optional[Dict[str, Any]]) -> None + self.options = options + if options and options["dsn"] is not None and options["dsn"]: + self.parsed_dsn = Dsn(options["dsn"]) + else: + self.parsed_dsn = None + + def capture_event(self, event): + # type: (Self, Event) -> None + """ + DEPRECATED: Please use capture_envelope instead. + + This gets invoked with the event dictionary when an event should + be sent to sentry. + """ + + warnings.warn( + "capture_event is deprecated, please use capture_envelope instead!", + DeprecationWarning, + stacklevel=2, + ) + + envelope = Envelope() + envelope.add_event(event) + self.capture_envelope(envelope) + + @abstractmethod + def capture_envelope(self, envelope): + # type: (Self, Envelope) -> None + """ + Send an envelope to Sentry. + + Envelopes are a data container format that can hold any type of data + submitted to Sentry. We use it to send all event data (including errors, + transactions, crons check-ins, etc.) to Sentry. + """ + pass + + def flush( + self, + timeout, + callback=None, + ): + # type: (Self, float, Optional[Any]) -> None + """ + Wait `timeout` seconds for the current events to be sent out. + + The default implementation is a no-op, since this method may only be relevant to some transports. + Subclasses should override this method if necessary. + """ + return None + + def kill(self): + # type: (Self) -> None + """ + Forcefully kills the transport. + + The default implementation is a no-op, since this method may only be relevant to some transports. + Subclasses should override this method if necessary. + """ + return None + + def record_lost_event( + self, + reason, # type: str + data_category=None, # type: Optional[EventDataCategory] + item=None, # type: Optional[Item] + *, + quantity=1, # type: int + ): + # type: (...) -> None + """This increments a counter for event loss by reason and + data category by the given positive-int quantity (default 1). + + If an item is provided, the data category and quantity are + extracted from the item, and the values passed for + data_category and quantity are ignored. + + When recording a lost transaction via data_category="transaction", + the calling code should also record the lost spans via this method. + When recording lost spans, `quantity` should be set to the number + of contained spans, plus one for the transaction itself. When + passing an Item containing a transaction via the `item` parameter, + this method automatically records the lost spans. + """ + return None + + def is_healthy(self): + # type: (Self) -> bool + return True + + def __del__(self): + # type: (Self) -> None + try: + self.kill() + except Exception: + pass + + +def _parse_rate_limits(header, now=None): + # type: (str, Optional[datetime]) -> Iterable[Tuple[Optional[EventDataCategory], datetime]] + if now is None: + now = datetime.now(timezone.utc) + + for limit in header.split(","): + try: + parameters = limit.strip().split(":") + retry_after_val, categories = parameters[:2] + + retry_after = now + timedelta(seconds=int(retry_after_val)) + for category in categories and categories.split(";") or (None,): + if category == "metric_bucket": + try: + namespaces = parameters[4].split(";") + except IndexError: + namespaces = [] + + if not namespaces or "custom" in namespaces: + yield category, retry_after # type: ignore + + else: + yield category, retry_after # type: ignore + except (LookupError, ValueError): + continue + + +class BaseHttpTransport(Transport): + """The base HTTP transport.""" + + def __init__(self, options): + # type: (Self, Dict[str, Any]) -> None + from sentry_sdk.consts import VERSION + + Transport.__init__(self, options) + assert self.parsed_dsn is not None + self.options = options # type: Dict[str, Any] + self._worker = BackgroundWorker(queue_size=options["transport_queue_size"]) + self._auth = self.parsed_dsn.to_auth("sentry.python/%s" % VERSION) + self._disabled_until = {} # type: Dict[Optional[EventDataCategory], datetime] + # We only use this Retry() class for the `get_retry_after` method it exposes + self._retry = urllib3.util.Retry() + self._discarded_events = defaultdict( + int + ) # type: DefaultDict[Tuple[EventDataCategory, str], int] + self._last_client_report_sent = time.time() + + self._pool = self._make_pool() + + # Backwards compatibility for deprecated `self.hub_class` attribute + self._hub_cls = sentry_sdk.Hub + + experiments = options.get("_experiments", {}) + compression_level = experiments.get( + "transport_compression_level", + experiments.get("transport_zlib_compression_level"), + ) + compression_algo = experiments.get( + "transport_compression_algo", + ( + "gzip" + # if only compression level is set, assume gzip for backwards compatibility + # if we don't have brotli available, fallback to gzip + if compression_level is not None or brotli is None + else "br" + ), + ) + + if compression_algo == "br" and brotli is None: + logger.warning( + "You asked for brotli compression without the Brotli module, falling back to gzip -9" + ) + compression_algo = "gzip" + compression_level = None + + if compression_algo not in ("br", "gzip"): + logger.warning( + "Unknown compression algo %s, disabling compression", compression_algo + ) + self._compression_level = 0 + self._compression_algo = None + else: + self._compression_algo = compression_algo + + if compression_level is not None: + self._compression_level = compression_level + elif self._compression_algo == "gzip": + self._compression_level = 9 + elif self._compression_algo == "br": + self._compression_level = 4 + + def record_lost_event( + self, + reason, # type: str + data_category=None, # type: Optional[EventDataCategory] + item=None, # type: Optional[Item] + *, + quantity=1, # type: int + ): + # type: (...) -> None + if not self.options["send_client_reports"]: + return + + if item is not None: + data_category = item.data_category + quantity = 1 # If an item is provided, we always count it as 1 (except for attachments, handled below). + + if data_category == "transaction": + # Also record the lost spans + event = item.get_transaction_event() or {} + + # +1 for the transaction itself + span_count = len(event.get("spans") or []) + 1 + self.record_lost_event(reason, "span", quantity=span_count) + + elif data_category == "attachment": + # quantity of 0 is actually 1 as we do not want to count + # empty attachments as actually empty. + quantity = len(item.get_bytes()) or 1 + + elif data_category is None: + raise TypeError("data category not provided") + + self._discarded_events[data_category, reason] += quantity + + def _get_header_value(self, response, header): + # type: (Self, Any, str) -> Optional[str] + return response.headers.get(header) + + def _update_rate_limits(self, response): + # type: (Self, Union[urllib3.BaseHTTPResponse, httpcore.Response]) -> None + + # new sentries with more rate limit insights. We honor this header + # no matter of the status code to update our internal rate limits. + header = self._get_header_value(response, "x-sentry-rate-limits") + if header: + logger.warning("Rate-limited via x-sentry-rate-limits") + self._disabled_until.update(_parse_rate_limits(header)) + + # old sentries only communicate global rate limit hits via the + # retry-after header on 429. This header can also be emitted on new + # sentries if a proxy in front wants to globally slow things down. + elif response.status == 429: + logger.warning("Rate-limited via 429") + retry_after_value = self._get_header_value(response, "Retry-After") + retry_after = ( + self._retry.parse_retry_after(retry_after_value) + if retry_after_value is not None + else None + ) or 60 + self._disabled_until[None] = datetime.now(timezone.utc) + timedelta( + seconds=retry_after + ) + + def _send_request( + self, + body, + headers, + endpoint_type=EndpointType.ENVELOPE, + envelope=None, + ): + # type: (Self, bytes, Dict[str, str], EndpointType, Optional[Envelope]) -> None + + def record_loss(reason): + # type: (str) -> None + if envelope is None: + self.record_lost_event(reason, data_category="error") + else: + for item in envelope.items: + self.record_lost_event(reason, item=item) + + headers.update( + { + "User-Agent": str(self._auth.client), + "X-Sentry-Auth": str(self._auth.to_header()), + } + ) + try: + response = self._request( + "POST", + endpoint_type, + body, + headers, + ) + except Exception: + self.on_dropped_event("network") + record_loss("network_error") + raise + + try: + self._update_rate_limits(response) + + if response.status == 429: + # if we hit a 429. Something was rate limited but we already + # acted on this in `self._update_rate_limits`. Note that we + # do not want to record event loss here as we will have recorded + # an outcome in relay already. + self.on_dropped_event("status_429") + pass + + elif response.status >= 300 or response.status < 200: + logger.error( + "Unexpected status code: %s (body: %s)", + response.status, + getattr(response, "data", getattr(response, "content", None)), + ) + self.on_dropped_event("status_{}".format(response.status)) + record_loss("network_error") + finally: + response.close() + + def on_dropped_event(self, _reason): + # type: (Self, str) -> None + return None + + def _fetch_pending_client_report(self, force=False, interval=60): + # type: (Self, bool, int) -> Optional[Item] + if not self.options["send_client_reports"]: + return None + + if not (force or self._last_client_report_sent < time.time() - interval): + return None + + discarded_events = self._discarded_events + self._discarded_events = defaultdict(int) + self._last_client_report_sent = time.time() + + if not discarded_events: + return None + + return Item( + PayloadRef( + json={ + "timestamp": time.time(), + "discarded_events": [ + {"reason": reason, "category": category, "quantity": quantity} + for ( + (category, reason), + quantity, + ) in discarded_events.items() + ], + } + ), + type="client_report", + ) + + def _flush_client_reports(self, force=False): + # type: (Self, bool) -> None + client_report = self._fetch_pending_client_report(force=force, interval=60) + if client_report is not None: + self.capture_envelope(Envelope(items=[client_report])) + + def _check_disabled(self, category): + # type: (str) -> bool + def _disabled(bucket): + # type: (Any) -> bool + + # The envelope item type used for metrics is statsd + # whereas the rate limit category is metric_bucket + if bucket == "statsd": + bucket = "metric_bucket" + + ts = self._disabled_until.get(bucket) + return ts is not None and ts > datetime.now(timezone.utc) + + return _disabled(category) or _disabled(None) + + def _is_rate_limited(self): + # type: (Self) -> bool + return any( + ts > datetime.now(timezone.utc) for ts in self._disabled_until.values() + ) + + def _is_worker_full(self): + # type: (Self) -> bool + return self._worker.full() + + def is_healthy(self): + # type: (Self) -> bool + return not (self._is_worker_full() or self._is_rate_limited()) + + def _send_envelope(self, envelope): + # type: (Self, Envelope) -> None + + # remove all items from the envelope which are over quota + new_items = [] + for item in envelope.items: + if self._check_disabled(item.data_category): + if item.data_category in ("transaction", "error", "default", "statsd"): + self.on_dropped_event("self_rate_limits") + self.record_lost_event("ratelimit_backoff", item=item) + else: + new_items.append(item) + + # Since we're modifying the envelope here make a copy so that others + # that hold references do not see their envelope modified. + envelope = Envelope(headers=envelope.headers, items=new_items) + + if not envelope.items: + return None + + # since we're already in the business of sending out an envelope here + # check if we have one pending for the stats session envelopes so we + # can attach it to this enveloped scheduled for sending. This will + # currently typically attach the client report to the most recent + # session update. + client_report_item = self._fetch_pending_client_report(interval=30) + if client_report_item is not None: + envelope.items.append(client_report_item) + + content_encoding, body = self._serialize_envelope(envelope) + + assert self.parsed_dsn is not None + logger.debug( + "Sending envelope [%s] project:%s host:%s", + envelope.description, + self.parsed_dsn.project_id, + self.parsed_dsn.host, + ) + + headers = { + "Content-Type": "application/x-sentry-envelope", + } + if content_encoding: + headers["Content-Encoding"] = content_encoding + + self._send_request( + body.getvalue(), + headers=headers, + endpoint_type=EndpointType.ENVELOPE, + envelope=envelope, + ) + return None + + def _serialize_envelope(self, envelope): + # type: (Self, Envelope) -> tuple[Optional[str], io.BytesIO] + content_encoding = None + body = io.BytesIO() + if self._compression_level == 0 or self._compression_algo is None: + envelope.serialize_into(body) + else: + content_encoding = self._compression_algo + if self._compression_algo == "br" and brotli is not None: + body.write( + brotli.compress( + envelope.serialize(), quality=self._compression_level + ) + ) + else: # assume gzip as we sanitize the algo value in init + with gzip.GzipFile( + fileobj=body, mode="w", compresslevel=self._compression_level + ) as f: + envelope.serialize_into(f) + + return content_encoding, body + + def _get_pool_options(self): + # type: (Self) -> Dict[str, Any] + raise NotImplementedError() + + def _in_no_proxy(self, parsed_dsn): + # type: (Self, Dsn) -> bool + no_proxy = getproxies().get("no") + if not no_proxy: + return False + for host in no_proxy.split(","): + host = host.strip() + if parsed_dsn.host.endswith(host) or parsed_dsn.netloc.endswith(host): + return True + return False + + def _make_pool(self): + # type: (Self) -> Union[PoolManager, ProxyManager, httpcore.SOCKSProxy, httpcore.HTTPProxy, httpcore.ConnectionPool] + raise NotImplementedError() + + def _request( + self, + method, + endpoint_type, + body, + headers, + ): + # type: (Self, str, EndpointType, Any, Mapping[str, str]) -> Union[urllib3.BaseHTTPResponse, httpcore.Response] + raise NotImplementedError() + + def capture_envelope( + self, envelope # type: Envelope + ): + # type: (...) -> None + def send_envelope_wrapper(): + # type: () -> None + with capture_internal_exceptions(): + self._send_envelope(envelope) + self._flush_client_reports() + + if not self._worker.submit(send_envelope_wrapper): + self.on_dropped_event("full_queue") + for item in envelope.items: + self.record_lost_event("queue_overflow", item=item) + + def flush( + self, + timeout, + callback=None, + ): + # type: (Self, float, Optional[Callable[[int, float], None]]) -> None + logger.debug("Flushing HTTP transport") + + if timeout > 0: + self._worker.submit(lambda: self._flush_client_reports(force=True)) + self._worker.flush(timeout, callback) + + def kill(self): + # type: (Self) -> None + logger.debug("Killing HTTP transport") + self._worker.kill() + + @staticmethod + def _warn_hub_cls(): + # type: () -> None + """Convenience method to warn users about the deprecation of the `hub_cls` attribute.""" + warnings.warn( + "The `hub_cls` attribute is deprecated and will be removed in a future release.", + DeprecationWarning, + stacklevel=3, + ) + + @property + def hub_cls(self): + # type: (Self) -> type[sentry_sdk.Hub] + """DEPRECATED: This attribute is deprecated and will be removed in a future release.""" + HttpTransport._warn_hub_cls() + return self._hub_cls + + @hub_cls.setter + def hub_cls(self, value): + # type: (Self, type[sentry_sdk.Hub]) -> None + """DEPRECATED: This attribute is deprecated and will be removed in a future release.""" + HttpTransport._warn_hub_cls() + self._hub_cls = value + + +class HttpTransport(BaseHttpTransport): + if TYPE_CHECKING: + _pool: Union[PoolManager, ProxyManager] + + def _get_pool_options(self): + # type: (Self) -> Dict[str, Any] + + num_pools = self.options.get("_experiments", {}).get("transport_num_pools") + options = { + "num_pools": 2 if num_pools is None else int(num_pools), + "cert_reqs": "CERT_REQUIRED", + } + + socket_options = None # type: Optional[List[Tuple[int, int, int | bytes]]] + + if self.options["socket_options"] is not None: + socket_options = self.options["socket_options"] + + if self.options["keep_alive"]: + if socket_options is None: + socket_options = [] + + used_options = {(o[0], o[1]) for o in socket_options} + for default_option in KEEP_ALIVE_SOCKET_OPTIONS: + if (default_option[0], default_option[1]) not in used_options: + socket_options.append(default_option) + + if socket_options is not None: + options["socket_options"] = socket_options + + options["ca_certs"] = ( + self.options["ca_certs"] # User-provided bundle from the SDK init + or os.environ.get("SSL_CERT_FILE") + or os.environ.get("REQUESTS_CA_BUNDLE") + or certifi.where() + ) + + options["cert_file"] = self.options["cert_file"] or os.environ.get( + "CLIENT_CERT_FILE" + ) + options["key_file"] = self.options["key_file"] or os.environ.get( + "CLIENT_KEY_FILE" + ) + + return options + + def _make_pool(self): + # type: (Self) -> Union[PoolManager, ProxyManager] + if self.parsed_dsn is None: + raise ValueError("Cannot create HTTP-based transport without valid DSN") + + proxy = None + no_proxy = self._in_no_proxy(self.parsed_dsn) + + # try HTTPS first + https_proxy = self.options["https_proxy"] + if self.parsed_dsn.scheme == "https" and (https_proxy != ""): + proxy = https_proxy or (not no_proxy and getproxies().get("https")) + + # maybe fallback to HTTP proxy + http_proxy = self.options["http_proxy"] + if not proxy and (http_proxy != ""): + proxy = http_proxy or (not no_proxy and getproxies().get("http")) + + opts = self._get_pool_options() + + if proxy: + proxy_headers = self.options["proxy_headers"] + if proxy_headers: + opts["proxy_headers"] = proxy_headers + + if proxy.startswith("socks"): + use_socks_proxy = True + try: + # Check if PySocks dependency is available + from urllib3.contrib.socks import SOCKSProxyManager + except ImportError: + use_socks_proxy = False + logger.warning( + "You have configured a SOCKS proxy (%s) but support for SOCKS proxies is not installed. Disabling proxy support. Please add `PySocks` (or `urllib3` with the `[socks]` extra) to your dependencies.", + proxy, + ) + + if use_socks_proxy: + return SOCKSProxyManager(proxy, **opts) + else: + return urllib3.PoolManager(**opts) + else: + return urllib3.ProxyManager(proxy, **opts) + else: + return urllib3.PoolManager(**opts) + + def _request( + self, + method, + endpoint_type, + body, + headers, + ): + # type: (Self, str, EndpointType, Any, Mapping[str, str]) -> urllib3.BaseHTTPResponse + return self._pool.request( + method, + self._auth.get_api_url(endpoint_type), + body=body, + headers=headers, + ) + + +try: + import httpcore + import h2 # type: ignore # noqa: F401 +except ImportError: + # Sorry, no Http2Transport for you + class Http2Transport(HttpTransport): + def __init__(self, options): + # type: (Self, Dict[str, Any]) -> None + super().__init__(options) + logger.warning( + "You tried to use HTTP2Transport but don't have httpcore[http2] installed. Falling back to HTTPTransport." + ) + +else: + + class Http2Transport(BaseHttpTransport): # type: ignore + """The HTTP2 transport based on httpcore.""" + + if TYPE_CHECKING: + _pool: Union[ + httpcore.SOCKSProxy, httpcore.HTTPProxy, httpcore.ConnectionPool + ] + + def _get_header_value(self, response, header): + # type: (Self, httpcore.Response, str) -> Optional[str] + return next( + ( + val.decode("ascii") + for key, val in response.headers + if key.decode("ascii").lower() == header + ), + None, + ) + + def _request( + self, + method, + endpoint_type, + body, + headers, + ): + # type: (Self, str, EndpointType, Any, Mapping[str, str]) -> httpcore.Response + response = self._pool.request( + method, + self._auth.get_api_url(endpoint_type), + content=body, + headers=headers, # type: ignore + ) + return response + + def _get_pool_options(self): + # type: (Self) -> Dict[str, Any] + options = { + "http2": self.parsed_dsn is not None + and self.parsed_dsn.scheme == "https", + "retries": 3, + } # type: Dict[str, Any] + + socket_options = ( + self.options["socket_options"] + if self.options["socket_options"] is not None + else [] + ) + + used_options = {(o[0], o[1]) for o in socket_options} + for default_option in KEEP_ALIVE_SOCKET_OPTIONS: + if (default_option[0], default_option[1]) not in used_options: + socket_options.append(default_option) + + options["socket_options"] = socket_options + + ssl_context = ssl.create_default_context() + ssl_context.load_verify_locations( + self.options["ca_certs"] # User-provided bundle from the SDK init + or os.environ.get("SSL_CERT_FILE") + or os.environ.get("REQUESTS_CA_BUNDLE") + or certifi.where() + ) + cert_file = self.options["cert_file"] or os.environ.get("CLIENT_CERT_FILE") + key_file = self.options["key_file"] or os.environ.get("CLIENT_KEY_FILE") + if cert_file is not None: + ssl_context.load_cert_chain(cert_file, key_file) + + options["ssl_context"] = ssl_context + + return options + + def _make_pool(self): + # type: (Self) -> Union[httpcore.SOCKSProxy, httpcore.HTTPProxy, httpcore.ConnectionPool] + if self.parsed_dsn is None: + raise ValueError("Cannot create HTTP-based transport without valid DSN") + proxy = None + no_proxy = self._in_no_proxy(self.parsed_dsn) + + # try HTTPS first + https_proxy = self.options["https_proxy"] + if self.parsed_dsn.scheme == "https" and (https_proxy != ""): + proxy = https_proxy or (not no_proxy and getproxies().get("https")) + + # maybe fallback to HTTP proxy + http_proxy = self.options["http_proxy"] + if not proxy and (http_proxy != ""): + proxy = http_proxy or (not no_proxy and getproxies().get("http")) + + opts = self._get_pool_options() + + if proxy: + proxy_headers = self.options["proxy_headers"] + if proxy_headers: + opts["proxy_headers"] = proxy_headers + + if proxy.startswith("socks"): + try: + if "socket_options" in opts: + socket_options = opts.pop("socket_options") + if socket_options: + logger.warning( + "You have defined socket_options but using a SOCKS proxy which doesn't support these. We'll ignore socket_options." + ) + return httpcore.SOCKSProxy(proxy_url=proxy, **opts) + except RuntimeError: + logger.warning( + "You have configured a SOCKS proxy (%s) but support for SOCKS proxies is not installed. Disabling proxy support.", + proxy, + ) + else: + return httpcore.HTTPProxy(proxy_url=proxy, **opts) + + return httpcore.ConnectionPool(**opts) + + +class _FunctionTransport(Transport): + """ + DEPRECATED: Users wishing to provide a custom transport should subclass + the Transport class, rather than providing a function. + """ + + def __init__( + self, func # type: Callable[[Event], None] + ): + # type: (...) -> None + Transport.__init__(self) + self._func = func + + def capture_event( + self, event # type: Event + ): + # type: (...) -> None + self._func(event) + return None + + def capture_envelope(self, envelope: Envelope) -> None: + # Since function transports expect to be called with an event, we need + # to iterate over the envelope and call the function for each event, via + # the deprecated capture_event method. + event = envelope.get_event() + if event is not None: + self.capture_event(event) + + +def make_transport(options): + # type: (Dict[str, Any]) -> Optional[Transport] + ref_transport = options["transport"] + + use_http2_transport = options.get("_experiments", {}).get("transport_http2", False) + + # By default, we use the http transport class + transport_cls = ( + Http2Transport if use_http2_transport else HttpTransport + ) # type: Type[Transport] + + if isinstance(ref_transport, Transport): + return ref_transport + elif isinstance(ref_transport, type) and issubclass(ref_transport, Transport): + transport_cls = ref_transport + elif callable(ref_transport): + warnings.warn( + "Function transports are deprecated and will be removed in a future release." + "Please provide a Transport instance or subclass, instead.", + DeprecationWarning, + stacklevel=2, + ) + return _FunctionTransport(ref_transport) + + # if a transport class is given only instantiate it if the dsn is not + # empty or None + if options["dsn"]: + return transport_cls(options) + + return None diff --git a/aws/lambda_demo/sentry_sdk/types.py b/aws/lambda_demo/sentry_sdk/types.py new file mode 100644 index 000000000..a81be8f1c --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/types.py @@ -0,0 +1,24 @@ +""" +This module contains type definitions for the Sentry SDK's public API. +The types are re-exported from the internal module `sentry_sdk._types`. + +Disclaimer: Since types are a form of documentation, type definitions +may change in minor releases. Removing a type would be considered a +breaking change, and so we will only remove type definitions in major +releases. +""" + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from sentry_sdk._types import Event, EventDataCategory, Hint +else: + from typing import Any + + # The lines below allow the types to be imported from outside `if TYPE_CHECKING` + # guards. The types in this module are only intended to be used for type hints. + Event = Any + EventDataCategory = Any + Hint = Any + +__all__ = ("Event", "EventDataCategory", "Hint") diff --git a/aws/lambda_demo/sentry_sdk/utils.py b/aws/lambda_demo/sentry_sdk/utils.py new file mode 100644 index 000000000..ae6e7538a --- /dev/null +++ b/aws/lambda_demo/sentry_sdk/utils.py @@ -0,0 +1,1958 @@ +import base64 +import json +import linecache +import logging +import math +import os +import random +import re +import subprocess +import sys +import threading +import time +from collections import namedtuple +from datetime import datetime, timezone +from decimal import Decimal +from functools import partial, partialmethod, wraps +from numbers import Real +from urllib.parse import parse_qs, unquote, urlencode, urlsplit, urlunsplit + +try: + # Python 3.11 + from builtins import BaseExceptionGroup +except ImportError: + # Python 3.10 and below + BaseExceptionGroup = None # type: ignore + +import sentry_sdk +from sentry_sdk._compat import PY37 +from sentry_sdk.consts import ( + DEFAULT_ADD_FULL_STACK, + DEFAULT_MAX_STACK_FRAMES, + DEFAULT_MAX_VALUE_LENGTH, + EndpointType, +) + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from types import FrameType, TracebackType + from typing import ( + Any, + Callable, + cast, + ContextManager, + Dict, + Iterator, + List, + NoReturn, + Optional, + overload, + ParamSpec, + Set, + Tuple, + Type, + TypeVar, + Union, + ) + + from gevent.hub import Hub + + from sentry_sdk._types import Event, ExcInfo + + P = ParamSpec("P") + R = TypeVar("R") + + +epoch = datetime(1970, 1, 1) + +# The logger is created here but initialized in the debug support module +logger = logging.getLogger("sentry_sdk.errors") + +_installed_modules = None + +BASE64_ALPHABET = re.compile(r"^[a-zA-Z0-9/+=]*$") + +SENSITIVE_DATA_SUBSTITUTE = "[Filtered]" + +FALSY_ENV_VALUES = frozenset(("false", "f", "n", "no", "off", "0")) +TRUTHY_ENV_VALUES = frozenset(("true", "t", "y", "yes", "on", "1")) + + +def env_to_bool(value, *, strict=False): + # type: (Any, Optional[bool]) -> bool | None + """Casts an ENV variable value to boolean using the constants defined above. + In strict mode, it may return None if the value doesn't match any of the predefined values. + """ + normalized = str(value).lower() if value is not None else None + + if normalized in FALSY_ENV_VALUES: + return False + + if normalized in TRUTHY_ENV_VALUES: + return True + + return None if strict else bool(value) + + +def json_dumps(data): + # type: (Any) -> bytes + """Serialize data into a compact JSON representation encoded as UTF-8.""" + return json.dumps(data, allow_nan=False, separators=(",", ":")).encode("utf-8") + + +def get_git_revision(): + # type: () -> Optional[str] + try: + with open(os.path.devnull, "w+") as null: + # prevent command prompt windows from popping up on windows + startupinfo = None + if sys.platform == "win32" or sys.platform == "cygwin": + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + + revision = ( + subprocess.Popen( + ["git", "rev-parse", "HEAD"], + startupinfo=startupinfo, + stdout=subprocess.PIPE, + stderr=null, + stdin=null, + ) + .communicate()[0] + .strip() + .decode("utf-8") + ) + except (OSError, IOError, FileNotFoundError): + return None + + return revision + + +def get_default_release(): + # type: () -> Optional[str] + """Try to guess a default release.""" + release = os.environ.get("SENTRY_RELEASE") + if release: + return release + + release = get_git_revision() + if release: + return release + + for var in ( + "HEROKU_SLUG_COMMIT", + "SOURCE_VERSION", + "CODEBUILD_RESOLVED_SOURCE_VERSION", + "CIRCLE_SHA1", + "GAE_DEPLOYMENT_ID", + ): + release = os.environ.get(var) + if release: + return release + return None + + +def get_sdk_name(installed_integrations): + # type: (List[str]) -> str + """Return the SDK name including the name of the used web framework.""" + + # Note: I can not use for example sentry_sdk.integrations.django.DjangoIntegration.identifier + # here because if django is not installed the integration is not accessible. + framework_integrations = [ + "django", + "flask", + "fastapi", + "bottle", + "falcon", + "quart", + "sanic", + "starlette", + "litestar", + "starlite", + "chalice", + "serverless", + "pyramid", + "tornado", + "aiohttp", + "aws_lambda", + "gcp", + "beam", + "asgi", + "wsgi", + ] + + for integration in framework_integrations: + if integration in installed_integrations: + return "sentry.python.{}".format(integration) + + return "sentry.python" + + +class CaptureInternalException: + __slots__ = () + + def __enter__(self): + # type: () -> ContextManager[Any] + return self + + def __exit__(self, ty, value, tb): + # type: (Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]) -> bool + if ty is not None and value is not None: + capture_internal_exception((ty, value, tb)) + + return True + + +_CAPTURE_INTERNAL_EXCEPTION = CaptureInternalException() + + +def capture_internal_exceptions(): + # type: () -> ContextManager[Any] + return _CAPTURE_INTERNAL_EXCEPTION + + +def capture_internal_exception(exc_info): + # type: (ExcInfo) -> None + """ + Capture an exception that is likely caused by a bug in the SDK + itself. + + These exceptions do not end up in Sentry and are just logged instead. + """ + if sentry_sdk.get_client().is_active(): + logger.error("Internal error in sentry_sdk", exc_info=exc_info) + + +def to_timestamp(value): + # type: (datetime) -> float + return (value - epoch).total_seconds() + + +def format_timestamp(value): + # type: (datetime) -> str + """Formats a timestamp in RFC 3339 format. + + Any datetime objects with a non-UTC timezone are converted to UTC, so that all timestamps are formatted in UTC. + """ + utctime = value.astimezone(timezone.utc) + + # We use this custom formatting rather than isoformat for backwards compatibility (we have used this format for + # several years now), and isoformat is slightly different. + return utctime.strftime("%Y-%m-%dT%H:%M:%S.%fZ") + + +ISO_TZ_SEPARATORS = frozenset(("+", "-")) + + +def datetime_from_isoformat(value): + # type: (str) -> datetime + try: + result = datetime.fromisoformat(value) + except (AttributeError, ValueError): + # py 3.6 + timestamp_format = ( + "%Y-%m-%dT%H:%M:%S.%f" if "." in value else "%Y-%m-%dT%H:%M:%S" + ) + if value.endswith("Z"): + value = value[:-1] + "+0000" + + if value[-6] in ISO_TZ_SEPARATORS: + timestamp_format += "%z" + value = value[:-3] + value[-2:] + elif value[-5] in ISO_TZ_SEPARATORS: + timestamp_format += "%z" + + result = datetime.strptime(value, timestamp_format) + return result.astimezone(timezone.utc) + + +def event_hint_with_exc_info(exc_info=None): + # type: (Optional[ExcInfo]) -> Dict[str, Optional[ExcInfo]] + """Creates a hint with the exc info filled in.""" + if exc_info is None: + exc_info = sys.exc_info() + else: + exc_info = exc_info_from_error(exc_info) + if exc_info[0] is None: + exc_info = None + return {"exc_info": exc_info} + + +class BadDsn(ValueError): + """Raised on invalid DSNs.""" + + +class Dsn: + """Represents a DSN.""" + + def __init__(self, value): + # type: (Union[Dsn, str]) -> None + if isinstance(value, Dsn): + self.__dict__ = dict(value.__dict__) + return + parts = urlsplit(str(value)) + + if parts.scheme not in ("http", "https"): + raise BadDsn("Unsupported scheme %r" % parts.scheme) + self.scheme = parts.scheme + + if parts.hostname is None: + raise BadDsn("Missing hostname") + + self.host = parts.hostname + + if parts.port is None: + self.port = self.scheme == "https" and 443 or 80 # type: int + else: + self.port = parts.port + + if not parts.username: + raise BadDsn("Missing public key") + + self.public_key = parts.username + self.secret_key = parts.password + + path = parts.path.rsplit("/", 1) + + try: + self.project_id = str(int(path.pop())) + except (ValueError, TypeError): + raise BadDsn("Invalid project in DSN (%r)" % (parts.path or "")[1:]) + + self.path = "/".join(path) + "/" + + @property + def netloc(self): + # type: () -> str + """The netloc part of a DSN.""" + rv = self.host + if (self.scheme, self.port) not in (("http", 80), ("https", 443)): + rv = "%s:%s" % (rv, self.port) + return rv + + def to_auth(self, client=None): + # type: (Optional[Any]) -> Auth + """Returns the auth info object for this dsn.""" + return Auth( + scheme=self.scheme, + host=self.netloc, + path=self.path, + project_id=self.project_id, + public_key=self.public_key, + secret_key=self.secret_key, + client=client, + ) + + def __str__(self): + # type: () -> str + return "%s://%s%s@%s%s%s" % ( + self.scheme, + self.public_key, + self.secret_key and "@" + self.secret_key or "", + self.netloc, + self.path, + self.project_id, + ) + + +class Auth: + """Helper object that represents the auth info.""" + + def __init__( + self, + scheme, + host, + project_id, + public_key, + secret_key=None, + version=7, + client=None, + path="/", + ): + # type: (str, str, str, str, Optional[str], int, Optional[Any], str) -> None + self.scheme = scheme + self.host = host + self.path = path + self.project_id = project_id + self.public_key = public_key + self.secret_key = secret_key + self.version = version + self.client = client + + def get_api_url( + self, type=EndpointType.ENVELOPE # type: EndpointType + ): + # type: (...) -> str + """Returns the API url for storing events.""" + return "%s://%s%sapi/%s/%s/" % ( + self.scheme, + self.host, + self.path, + self.project_id, + type.value, + ) + + def to_header(self): + # type: () -> str + """Returns the auth header a string.""" + rv = [("sentry_key", self.public_key), ("sentry_version", self.version)] + if self.client is not None: + rv.append(("sentry_client", self.client)) + if self.secret_key is not None: + rv.append(("sentry_secret", self.secret_key)) + return "Sentry " + ", ".join("%s=%s" % (key, value) for key, value in rv) + + +class AnnotatedValue: + """ + Meta information for a data field in the event payload. + This is to tell Relay that we have tampered with the fields value. + See: + https://github.com/getsentry/relay/blob/be12cd49a0f06ea932ed9b9f93a655de5d6ad6d1/relay-general/src/types/meta.rs#L407-L423 + """ + + __slots__ = ("value", "metadata") + + def __init__(self, value, metadata): + # type: (Optional[Any], Dict[str, Any]) -> None + self.value = value + self.metadata = metadata + + def __eq__(self, other): + # type: (Any) -> bool + if not isinstance(other, AnnotatedValue): + return False + + return self.value == other.value and self.metadata == other.metadata + + @classmethod + def removed_because_raw_data(cls): + # type: () -> AnnotatedValue + """The value was removed because it could not be parsed. This is done for request body values that are not json nor a form.""" + return AnnotatedValue( + value="", + metadata={ + "rem": [ # Remark + [ + "!raw", # Unparsable raw data + "x", # The fields original value was removed + ] + ] + }, + ) + + @classmethod + def removed_because_over_size_limit(cls): + # type: () -> AnnotatedValue + """The actual value was removed because the size of the field exceeded the configured maximum size (specified with the max_request_body_size sdk option)""" + return AnnotatedValue( + value="", + metadata={ + "rem": [ # Remark + [ + "!config", # Because of configured maximum size + "x", # The fields original value was removed + ] + ] + }, + ) + + @classmethod + def substituted_because_contains_sensitive_data(cls): + # type: () -> AnnotatedValue + """The actual value was removed because it contained sensitive information.""" + return AnnotatedValue( + value=SENSITIVE_DATA_SUBSTITUTE, + metadata={ + "rem": [ # Remark + [ + "!config", # Because of SDK configuration (in this case the config is the hard coded removal of certain django cookies) + "s", # The fields original value was substituted + ] + ] + }, + ) + + +if TYPE_CHECKING: + from typing import TypeVar + + T = TypeVar("T") + Annotated = Union[AnnotatedValue, T] + + +def get_type_name(cls): + # type: (Optional[type]) -> Optional[str] + return getattr(cls, "__qualname__", None) or getattr(cls, "__name__", None) + + +def get_type_module(cls): + # type: (Optional[type]) -> Optional[str] + mod = getattr(cls, "__module__", None) + if mod not in (None, "builtins", "__builtins__"): + return mod + return None + + +def should_hide_frame(frame): + # type: (FrameType) -> bool + try: + mod = frame.f_globals["__name__"] + if mod.startswith("sentry_sdk."): + return True + except (AttributeError, KeyError): + pass + + for flag_name in "__traceback_hide__", "__tracebackhide__": + try: + if frame.f_locals[flag_name]: + return True + except Exception: + pass + + return False + + +def iter_stacks(tb): + # type: (Optional[TracebackType]) -> Iterator[TracebackType] + tb_ = tb # type: Optional[TracebackType] + while tb_ is not None: + if not should_hide_frame(tb_.tb_frame): + yield tb_ + tb_ = tb_.tb_next + + +def get_lines_from_file( + filename, # type: str + lineno, # type: int + max_length=None, # type: Optional[int] + loader=None, # type: Optional[Any] + module=None, # type: Optional[str] +): + # type: (...) -> Tuple[List[Annotated[str]], Optional[Annotated[str]], List[Annotated[str]]] + context_lines = 5 + source = None + if loader is not None and hasattr(loader, "get_source"): + try: + source_str = loader.get_source(module) # type: Optional[str] + except (ImportError, IOError): + source_str = None + if source_str is not None: + source = source_str.splitlines() + + if source is None: + try: + source = linecache.getlines(filename) + except (OSError, IOError): + return [], None, [] + + if not source: + return [], None, [] + + lower_bound = max(0, lineno - context_lines) + upper_bound = min(lineno + 1 + context_lines, len(source)) + + try: + pre_context = [ + strip_string(line.strip("\r\n"), max_length=max_length) + for line in source[lower_bound:lineno] + ] + context_line = strip_string(source[lineno].strip("\r\n"), max_length=max_length) + post_context = [ + strip_string(line.strip("\r\n"), max_length=max_length) + for line in source[(lineno + 1) : upper_bound] + ] + return pre_context, context_line, post_context + except IndexError: + # the file may have changed since it was loaded into memory + return [], None, [] + + +def get_source_context( + frame, # type: FrameType + tb_lineno, # type: int + max_value_length=None, # type: Optional[int] +): + # type: (...) -> Tuple[List[Annotated[str]], Optional[Annotated[str]], List[Annotated[str]]] + try: + abs_path = frame.f_code.co_filename # type: Optional[str] + except Exception: + abs_path = None + try: + module = frame.f_globals["__name__"] + except Exception: + return [], None, [] + try: + loader = frame.f_globals["__loader__"] + except Exception: + loader = None + lineno = tb_lineno - 1 + if lineno is not None and abs_path: + return get_lines_from_file( + abs_path, lineno, max_value_length, loader=loader, module=module + ) + return [], None, [] + + +def safe_str(value): + # type: (Any) -> str + try: + return str(value) + except Exception: + return safe_repr(value) + + +def safe_repr(value): + # type: (Any) -> str + try: + return repr(value) + except Exception: + return "" + + +def filename_for_module(module, abs_path): + # type: (Optional[str], Optional[str]) -> Optional[str] + if not abs_path or not module: + return abs_path + + try: + if abs_path.endswith(".pyc"): + abs_path = abs_path[:-1] + + base_module = module.split(".", 1)[0] + if base_module == module: + return os.path.basename(abs_path) + + base_module_path = sys.modules[base_module].__file__ + if not base_module_path: + return abs_path + + return abs_path.split(base_module_path.rsplit(os.sep, 2)[0], 1)[-1].lstrip( + os.sep + ) + except Exception: + return abs_path + + +def serialize_frame( + frame, + tb_lineno=None, + include_local_variables=True, + include_source_context=True, + max_value_length=None, + custom_repr=None, +): + # type: (FrameType, Optional[int], bool, bool, Optional[int], Optional[Callable[..., Optional[str]]]) -> Dict[str, Any] + f_code = getattr(frame, "f_code", None) + if not f_code: + abs_path = None + function = None + else: + abs_path = frame.f_code.co_filename + function = frame.f_code.co_name + try: + module = frame.f_globals["__name__"] + except Exception: + module = None + + if tb_lineno is None: + tb_lineno = frame.f_lineno + + rv = { + "filename": filename_for_module(module, abs_path) or None, + "abs_path": os.path.abspath(abs_path) if abs_path else None, + "function": function or "", + "module": module, + "lineno": tb_lineno, + } # type: Dict[str, Any] + + if include_source_context: + rv["pre_context"], rv["context_line"], rv["post_context"] = get_source_context( + frame, tb_lineno, max_value_length + ) + + if include_local_variables: + from sentry_sdk.serializer import serialize + + rv["vars"] = serialize( + dict(frame.f_locals), is_vars=True, custom_repr=custom_repr + ) + + return rv + + +def current_stacktrace( + include_local_variables=True, # type: bool + include_source_context=True, # type: bool + max_value_length=None, # type: Optional[int] +): + # type: (...) -> Dict[str, Any] + __tracebackhide__ = True + frames = [] + + f = sys._getframe() # type: Optional[FrameType] + while f is not None: + if not should_hide_frame(f): + frames.append( + serialize_frame( + f, + include_local_variables=include_local_variables, + include_source_context=include_source_context, + max_value_length=max_value_length, + ) + ) + f = f.f_back + + frames.reverse() + + return {"frames": frames} + + +def get_errno(exc_value): + # type: (BaseException) -> Optional[Any] + return getattr(exc_value, "errno", None) + + +def get_error_message(exc_value): + # type: (Optional[BaseException]) -> str + message = ( + getattr(exc_value, "message", "") + or getattr(exc_value, "detail", "") + or safe_str(exc_value) + ) # type: str + + # __notes__ should be a list of strings when notes are added + # via add_note, but can be anything else if __notes__ is set + # directly. We only support strings in __notes__, since that + # is the correct use. + notes = getattr(exc_value, "__notes__", None) # type: object + if isinstance(notes, list) and len(notes) > 0: + message += "\n" + "\n".join(note for note in notes if isinstance(note, str)) + + return message + + +def single_exception_from_error_tuple( + exc_type, # type: Optional[type] + exc_value, # type: Optional[BaseException] + tb, # type: Optional[TracebackType] + client_options=None, # type: Optional[Dict[str, Any]] + mechanism=None, # type: Optional[Dict[str, Any]] + exception_id=None, # type: Optional[int] + parent_id=None, # type: Optional[int] + source=None, # type: Optional[str] + full_stack=None, # type: Optional[list[dict[str, Any]]] +): + # type: (...) -> Dict[str, Any] + """ + Creates a dict that goes into the events `exception.values` list and is ingestible by Sentry. + + See the Exception Interface documentation for more details: + https://develop.sentry.dev/sdk/event-payloads/exception/ + """ + exception_value = {} # type: Dict[str, Any] + exception_value["mechanism"] = ( + mechanism.copy() if mechanism else {"type": "generic", "handled": True} + ) + if exception_id is not None: + exception_value["mechanism"]["exception_id"] = exception_id + + if exc_value is not None: + errno = get_errno(exc_value) + else: + errno = None + + if errno is not None: + exception_value["mechanism"].setdefault("meta", {}).setdefault( + "errno", {} + ).setdefault("number", errno) + + if source is not None: + exception_value["mechanism"]["source"] = source + + is_root_exception = exception_id == 0 + if not is_root_exception and parent_id is not None: + exception_value["mechanism"]["parent_id"] = parent_id + exception_value["mechanism"]["type"] = "chained" + + if is_root_exception and "type" not in exception_value["mechanism"]: + exception_value["mechanism"]["type"] = "generic" + + is_exception_group = BaseExceptionGroup is not None and isinstance( + exc_value, BaseExceptionGroup + ) + if is_exception_group: + exception_value["mechanism"]["is_exception_group"] = True + + exception_value["module"] = get_type_module(exc_type) + exception_value["type"] = get_type_name(exc_type) + exception_value["value"] = get_error_message(exc_value) + + if client_options is None: + include_local_variables = True + include_source_context = True + max_value_length = DEFAULT_MAX_VALUE_LENGTH # fallback + custom_repr = None + else: + include_local_variables = client_options["include_local_variables"] + include_source_context = client_options["include_source_context"] + max_value_length = client_options["max_value_length"] + custom_repr = client_options.get("custom_repr") + + frames = [ + serialize_frame( + tb.tb_frame, + tb_lineno=tb.tb_lineno, + include_local_variables=include_local_variables, + include_source_context=include_source_context, + max_value_length=max_value_length, + custom_repr=custom_repr, + ) + for tb in iter_stacks(tb) + ] # type: List[Dict[str, Any]] + + if frames: + if not full_stack: + new_frames = frames + else: + new_frames = merge_stack_frames(frames, full_stack, client_options) + + exception_value["stacktrace"] = {"frames": new_frames} + + return exception_value + + +HAS_CHAINED_EXCEPTIONS = hasattr(Exception, "__suppress_context__") + +if HAS_CHAINED_EXCEPTIONS: + + def walk_exception_chain(exc_info): + # type: (ExcInfo) -> Iterator[ExcInfo] + exc_type, exc_value, tb = exc_info + + seen_exceptions = [] + seen_exception_ids = set() # type: Set[int] + + while ( + exc_type is not None + and exc_value is not None + and id(exc_value) not in seen_exception_ids + ): + yield exc_type, exc_value, tb + + # Avoid hashing random types we don't know anything + # about. Use the list to keep a ref so that the `id` is + # not used for another object. + seen_exceptions.append(exc_value) + seen_exception_ids.add(id(exc_value)) + + if exc_value.__suppress_context__: + cause = exc_value.__cause__ + else: + cause = exc_value.__context__ + if cause is None: + break + exc_type = type(cause) + exc_value = cause + tb = getattr(cause, "__traceback__", None) + +else: + + def walk_exception_chain(exc_info): + # type: (ExcInfo) -> Iterator[ExcInfo] + yield exc_info + + +def exceptions_from_error( + exc_type, # type: Optional[type] + exc_value, # type: Optional[BaseException] + tb, # type: Optional[TracebackType] + client_options=None, # type: Optional[Dict[str, Any]] + mechanism=None, # type: Optional[Dict[str, Any]] + exception_id=0, # type: int + parent_id=0, # type: int + source=None, # type: Optional[str] + full_stack=None, # type: Optional[list[dict[str, Any]]] +): + # type: (...) -> Tuple[int, List[Dict[str, Any]]] + """ + Creates the list of exceptions. + This can include chained exceptions and exceptions from an ExceptionGroup. + + See the Exception Interface documentation for more details: + https://develop.sentry.dev/sdk/event-payloads/exception/ + """ + + parent = single_exception_from_error_tuple( + exc_type=exc_type, + exc_value=exc_value, + tb=tb, + client_options=client_options, + mechanism=mechanism, + exception_id=exception_id, + parent_id=parent_id, + source=source, + full_stack=full_stack, + ) + exceptions = [parent] + + parent_id = exception_id + exception_id += 1 + + should_supress_context = hasattr(exc_value, "__suppress_context__") and exc_value.__suppress_context__ # type: ignore + if should_supress_context: + # Add direct cause. + # The field `__cause__` is set when raised with the exception (using the `from` keyword). + exception_has_cause = ( + exc_value + and hasattr(exc_value, "__cause__") + and exc_value.__cause__ is not None + ) + if exception_has_cause: + cause = exc_value.__cause__ # type: ignore + (exception_id, child_exceptions) = exceptions_from_error( + exc_type=type(cause), + exc_value=cause, + tb=getattr(cause, "__traceback__", None), + client_options=client_options, + mechanism=mechanism, + exception_id=exception_id, + source="__cause__", + full_stack=full_stack, + ) + exceptions.extend(child_exceptions) + + else: + # Add indirect cause. + # The field `__context__` is assigned if another exception occurs while handling the exception. + exception_has_content = ( + exc_value + and hasattr(exc_value, "__context__") + and exc_value.__context__ is not None + ) + if exception_has_content: + context = exc_value.__context__ # type: ignore + (exception_id, child_exceptions) = exceptions_from_error( + exc_type=type(context), + exc_value=context, + tb=getattr(context, "__traceback__", None), + client_options=client_options, + mechanism=mechanism, + exception_id=exception_id, + source="__context__", + full_stack=full_stack, + ) + exceptions.extend(child_exceptions) + + # Add exceptions from an ExceptionGroup. + is_exception_group = exc_value and hasattr(exc_value, "exceptions") + if is_exception_group: + for idx, e in enumerate(exc_value.exceptions): # type: ignore + (exception_id, child_exceptions) = exceptions_from_error( + exc_type=type(e), + exc_value=e, + tb=getattr(e, "__traceback__", None), + client_options=client_options, + mechanism=mechanism, + exception_id=exception_id, + parent_id=parent_id, + source="exceptions[%s]" % idx, + full_stack=full_stack, + ) + exceptions.extend(child_exceptions) + + return (exception_id, exceptions) + + +def exceptions_from_error_tuple( + exc_info, # type: ExcInfo + client_options=None, # type: Optional[Dict[str, Any]] + mechanism=None, # type: Optional[Dict[str, Any]] + full_stack=None, # type: Optional[list[dict[str, Any]]] +): + # type: (...) -> List[Dict[str, Any]] + exc_type, exc_value, tb = exc_info + + is_exception_group = BaseExceptionGroup is not None and isinstance( + exc_value, BaseExceptionGroup + ) + + if is_exception_group: + (_, exceptions) = exceptions_from_error( + exc_type=exc_type, + exc_value=exc_value, + tb=tb, + client_options=client_options, + mechanism=mechanism, + exception_id=0, + parent_id=0, + full_stack=full_stack, + ) + + else: + exceptions = [] + for exc_type, exc_value, tb in walk_exception_chain(exc_info): + exceptions.append( + single_exception_from_error_tuple( + exc_type=exc_type, + exc_value=exc_value, + tb=tb, + client_options=client_options, + mechanism=mechanism, + full_stack=full_stack, + ) + ) + + exceptions.reverse() + + return exceptions + + +def to_string(value): + # type: (str) -> str + try: + return str(value) + except UnicodeDecodeError: + return repr(value)[1:-1] + + +def iter_event_stacktraces(event): + # type: (Event) -> Iterator[Dict[str, Any]] + if "stacktrace" in event: + yield event["stacktrace"] + if "threads" in event: + for thread in event["threads"].get("values") or (): + if "stacktrace" in thread: + yield thread["stacktrace"] + if "exception" in event: + for exception in event["exception"].get("values") or (): + if "stacktrace" in exception: + yield exception["stacktrace"] + + +def iter_event_frames(event): + # type: (Event) -> Iterator[Dict[str, Any]] + for stacktrace in iter_event_stacktraces(event): + for frame in stacktrace.get("frames") or (): + yield frame + + +def handle_in_app(event, in_app_exclude=None, in_app_include=None, project_root=None): + # type: (Event, Optional[List[str]], Optional[List[str]], Optional[str]) -> Event + for stacktrace in iter_event_stacktraces(event): + set_in_app_in_frames( + stacktrace.get("frames"), + in_app_exclude=in_app_exclude, + in_app_include=in_app_include, + project_root=project_root, + ) + + return event + + +def set_in_app_in_frames(frames, in_app_exclude, in_app_include, project_root=None): + # type: (Any, Optional[List[str]], Optional[List[str]], Optional[str]) -> Optional[Any] + if not frames: + return None + + for frame in frames: + # if frame has already been marked as in_app, skip it + current_in_app = frame.get("in_app") + if current_in_app is not None: + continue + + module = frame.get("module") + + # check if module in frame is in the list of modules to include + if _module_in_list(module, in_app_include): + frame["in_app"] = True + continue + + # check if module in frame is in the list of modules to exclude + if _module_in_list(module, in_app_exclude): + frame["in_app"] = False + continue + + # if frame has no abs_path, skip further checks + abs_path = frame.get("abs_path") + if abs_path is None: + continue + + if _is_external_source(abs_path): + frame["in_app"] = False + continue + + if _is_in_project_root(abs_path, project_root): + frame["in_app"] = True + continue + + return frames + + +def exc_info_from_error(error): + # type: (Union[BaseException, ExcInfo]) -> ExcInfo + if isinstance(error, tuple) and len(error) == 3: + exc_type, exc_value, tb = error + elif isinstance(error, BaseException): + tb = getattr(error, "__traceback__", None) + if tb is not None: + exc_type = type(error) + exc_value = error + else: + exc_type, exc_value, tb = sys.exc_info() + if exc_value is not error: + tb = None + exc_value = error + exc_type = type(error) + + else: + raise ValueError("Expected Exception object to report, got %s!" % type(error)) + + exc_info = (exc_type, exc_value, tb) + + if TYPE_CHECKING: + # This cast is safe because exc_type and exc_value are either both + # None or both not None. + exc_info = cast(ExcInfo, exc_info) + + return exc_info + + +def merge_stack_frames(frames, full_stack, client_options): + # type: (List[Dict[str, Any]], List[Dict[str, Any]], Optional[Dict[str, Any]]) -> List[Dict[str, Any]] + """ + Add the missing frames from full_stack to frames and return the merged list. + """ + frame_ids = { + ( + frame["abs_path"], + frame["context_line"], + frame["lineno"], + frame["function"], + ) + for frame in frames + } + + new_frames = [ + stackframe + for stackframe in full_stack + if ( + stackframe["abs_path"], + stackframe["context_line"], + stackframe["lineno"], + stackframe["function"], + ) + not in frame_ids + ] + new_frames.extend(frames) + + # Limit the number of frames + max_stack_frames = ( + client_options.get("max_stack_frames", DEFAULT_MAX_STACK_FRAMES) + if client_options + else None + ) + if max_stack_frames is not None: + new_frames = new_frames[len(new_frames) - max_stack_frames :] + + return new_frames + + +def event_from_exception( + exc_info, # type: Union[BaseException, ExcInfo] + client_options=None, # type: Optional[Dict[str, Any]] + mechanism=None, # type: Optional[Dict[str, Any]] +): + # type: (...) -> Tuple[Event, Dict[str, Any]] + exc_info = exc_info_from_error(exc_info) + hint = event_hint_with_exc_info(exc_info) + + if client_options and client_options.get("add_full_stack", DEFAULT_ADD_FULL_STACK): + full_stack = current_stacktrace( + include_local_variables=client_options["include_local_variables"], + max_value_length=client_options["max_value_length"], + )["frames"] + else: + full_stack = None + + return ( + { + "level": "error", + "exception": { + "values": exceptions_from_error_tuple( + exc_info, client_options, mechanism, full_stack + ) + }, + }, + hint, + ) + + +def _module_in_list(name, items): + # type: (Optional[str], Optional[List[str]]) -> bool + if name is None: + return False + + if not items: + return False + + for item in items: + if item == name or name.startswith(item + "."): + return True + + return False + + +def _is_external_source(abs_path): + # type: (Optional[str]) -> bool + # check if frame is in 'site-packages' or 'dist-packages' + if abs_path is None: + return False + + external_source = ( + re.search(r"[\\/](?:dist|site)-packages[\\/]", abs_path) is not None + ) + return external_source + + +def _is_in_project_root(abs_path, project_root): + # type: (Optional[str], Optional[str]) -> bool + if abs_path is None or project_root is None: + return False + + # check if path is in the project root + if abs_path.startswith(project_root): + return True + + return False + + +def _truncate_by_bytes(string, max_bytes): + # type: (str, int) -> str + """ + Truncate a UTF-8-encodable string to the last full codepoint so that it fits in max_bytes. + """ + truncated = string.encode("utf-8")[: max_bytes - 3].decode("utf-8", errors="ignore") + + return truncated + "..." + + +def _get_size_in_bytes(value): + # type: (str) -> Optional[int] + try: + return len(value.encode("utf-8")) + except (UnicodeEncodeError, UnicodeDecodeError): + return None + + +def strip_string(value, max_length=None): + # type: (str, Optional[int]) -> Union[AnnotatedValue, str] + if not value: + return value + + if max_length is None: + max_length = DEFAULT_MAX_VALUE_LENGTH + + byte_size = _get_size_in_bytes(value) + text_size = len(value) + + if byte_size is not None and byte_size > max_length: + # truncate to max_length bytes, preserving code points + truncated_value = _truncate_by_bytes(value, max_length) + elif text_size is not None and text_size > max_length: + # fallback to truncating by string length + truncated_value = value[: max_length - 3] + "..." + else: + return value + + return AnnotatedValue( + value=truncated_value, + metadata={ + "len": byte_size or text_size, + "rem": [["!limit", "x", max_length - 3, max_length]], + }, + ) + + +def parse_version(version): + # type: (str) -> Optional[Tuple[int, ...]] + """ + Parses a version string into a tuple of integers. + This uses the parsing loging from PEP 440: + https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions + """ + VERSION_PATTERN = r""" # noqa: N806 + v? + (?: + (?:(?P[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P

                                              # pre-release
    +                [-_\.]?
    +                (?P(a|b|c|rc|alpha|beta|pre|preview))
    +                [-_\.]?
    +                (?P[0-9]+)?
    +            )?
    +            (?P                                         # post release
    +                (?:-(?P[0-9]+))
    +                |
    +                (?:
    +                    [-_\.]?
    +                    (?Ppost|rev|r)
    +                    [-_\.]?
    +                    (?P[0-9]+)?
    +                )
    +            )?
    +            (?P                                          # dev release
    +                [-_\.]?
    +                (?Pdev)
    +                [-_\.]?
    +                (?P[0-9]+)?
    +            )?
    +        )
    +        (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
    +    """
    +
    +    pattern = re.compile(
    +        r"^\s*" + VERSION_PATTERN + r"\s*$",
    +        re.VERBOSE | re.IGNORECASE,
    +    )
    +
    +    try:
    +        release = pattern.match(version).groupdict()["release"]  # type: ignore
    +        release_tuple = tuple(map(int, release.split(".")[:3]))  # type: Tuple[int, ...]
    +    except (TypeError, ValueError, AttributeError):
    +        return None
    +
    +    return release_tuple
    +
    +
    +def _is_contextvars_broken():
    +    # type: () -> bool
    +    """
    +    Returns whether gevent/eventlet have patched the stdlib in a way where thread locals are now more "correct" than contextvars.
    +    """
    +    try:
    +        import gevent
    +        from gevent.monkey import is_object_patched
    +
    +        # Get the MAJOR and MINOR version numbers of Gevent
    +        version_tuple = tuple(
    +            [int(part) for part in re.split(r"a|b|rc|\.", gevent.__version__)[:2]]
    +        )
    +        if is_object_patched("threading", "local"):
    +            # Gevent 20.9.0 depends on Greenlet 0.4.17 which natively handles switching
    +            # context vars when greenlets are switched, so, Gevent 20.9.0+ is all fine.
    +            # Ref: https://github.com/gevent/gevent/blob/83c9e2ae5b0834b8f84233760aabe82c3ba065b4/src/gevent/monkey.py#L604-L609
    +            # Gevent 20.5, that doesn't depend on Greenlet 0.4.17 with native support
    +            # for contextvars, is able to patch both thread locals and contextvars, in
    +            # that case, check if contextvars are effectively patched.
    +            if (
    +                # Gevent 20.9.0+
    +                (sys.version_info >= (3, 7) and version_tuple >= (20, 9))
    +                # Gevent 20.5.0+ or Python < 3.7
    +                or (is_object_patched("contextvars", "ContextVar"))
    +            ):
    +                return False
    +
    +            return True
    +    except ImportError:
    +        pass
    +
    +    try:
    +        import greenlet
    +        from eventlet.patcher import is_monkey_patched  # type: ignore
    +
    +        greenlet_version = parse_version(greenlet.__version__)
    +
    +        if greenlet_version is None:
    +            logger.error(
    +                "Internal error in Sentry SDK: Could not parse Greenlet version from greenlet.__version__."
    +            )
    +            return False
    +
    +        if is_monkey_patched("thread") and greenlet_version < (0, 5):
    +            return True
    +    except ImportError:
    +        pass
    +
    +    return False
    +
    +
    +def _make_threadlocal_contextvars(local):
    +    # type: (type) -> type
    +    class ContextVar:
    +        # Super-limited impl of ContextVar
    +
    +        def __init__(self, name, default=None):
    +            # type: (str, Any) -> None
    +            self._name = name
    +            self._default = default
    +            self._local = local()
    +            self._original_local = local()
    +
    +        def get(self, default=None):
    +            # type: (Any) -> Any
    +            return getattr(self._local, "value", default or self._default)
    +
    +        def set(self, value):
    +            # type: (Any) -> Any
    +            token = str(random.getrandbits(64))
    +            original_value = self.get()
    +            setattr(self._original_local, token, original_value)
    +            self._local.value = value
    +            return token
    +
    +        def reset(self, token):
    +            # type: (Any) -> None
    +            self._local.value = getattr(self._original_local, token)
    +            # delete the original value (this way it works in Python 3.6+)
    +            del self._original_local.__dict__[token]
    +
    +    return ContextVar
    +
    +
    +def _get_contextvars():
    +    # type: () -> Tuple[bool, type]
    +    """
    +    Figure out the "right" contextvars installation to use. Returns a
    +    `contextvars.ContextVar`-like class with a limited API.
    +
    +    See https://docs.sentry.io/platforms/python/contextvars/ for more information.
    +    """
    +    if not _is_contextvars_broken():
    +        # aiocontextvars is a PyPI package that ensures that the contextvars
    +        # backport (also a PyPI package) works with asyncio under Python 3.6
    +        #
    +        # Import it if available.
    +        if sys.version_info < (3, 7):
    +            # `aiocontextvars` is absolutely required for functional
    +            # contextvars on Python 3.6.
    +            try:
    +                from aiocontextvars import ContextVar
    +
    +                return True, ContextVar
    +            except ImportError:
    +                pass
    +        else:
    +            # On Python 3.7 contextvars are functional.
    +            try:
    +                from contextvars import ContextVar
    +
    +                return True, ContextVar
    +            except ImportError:
    +                pass
    +
    +    # Fall back to basic thread-local usage.
    +
    +    from threading import local
    +
    +    return False, _make_threadlocal_contextvars(local)
    +
    +
    +HAS_REAL_CONTEXTVARS, ContextVar = _get_contextvars()
    +
    +CONTEXTVARS_ERROR_MESSAGE = """
    +
    +With asyncio/ASGI applications, the Sentry SDK requires a functional
    +installation of `contextvars` to avoid leaking scope/context data across
    +requests.
    +
    +Please refer to https://docs.sentry.io/platforms/python/contextvars/ for more information.
    +"""
    +
    +
    +def qualname_from_function(func):
    +    # type: (Callable[..., Any]) -> Optional[str]
    +    """Return the qualified name of func. Works with regular function, lambda, partial and partialmethod."""
    +    func_qualname = None  # type: Optional[str]
    +
    +    # Python 2
    +    try:
    +        return "%s.%s.%s" % (
    +            func.im_class.__module__,  # type: ignore
    +            func.im_class.__name__,  # type: ignore
    +            func.__name__,
    +        )
    +    except Exception:
    +        pass
    +
    +    prefix, suffix = "", ""
    +
    +    if isinstance(func, partial) and hasattr(func.func, "__name__"):
    +        prefix, suffix = "partial()"
    +        func = func.func
    +    else:
    +        # The _partialmethod attribute of methods wrapped with partialmethod() was renamed to __partialmethod__ in CPython 3.13:
    +        # https://github.com/python/cpython/pull/16600
    +        partial_method = getattr(func, "_partialmethod", None) or getattr(
    +            func, "__partialmethod__", None
    +        )
    +        if isinstance(partial_method, partialmethod):
    +            prefix, suffix = "partialmethod()"
    +            func = partial_method.func
    +
    +    if hasattr(func, "__qualname__"):
    +        func_qualname = func.__qualname__
    +    elif hasattr(func, "__name__"):  # Python 2.7 has no __qualname__
    +        func_qualname = func.__name__
    +
    +    # Python 3: methods, functions, classes
    +    if func_qualname is not None:
    +        if hasattr(func, "__module__"):
    +            func_qualname = func.__module__ + "." + func_qualname
    +        func_qualname = prefix + func_qualname + suffix
    +
    +    return func_qualname
    +
    +
    +def transaction_from_function(func):
    +    # type: (Callable[..., Any]) -> Optional[str]
    +    return qualname_from_function(func)
    +
    +
    +disable_capture_event = ContextVar("disable_capture_event")
    +
    +
    +class ServerlessTimeoutWarning(Exception):  # noqa: N818
    +    """Raised when a serverless method is about to reach its timeout."""
    +
    +    pass
    +
    +
    +class TimeoutThread(threading.Thread):
    +    """Creates a Thread which runs (sleeps) for a time duration equal to
    +    waiting_time and raises a custom ServerlessTimeout exception.
    +    """
    +
    +    def __init__(self, waiting_time, configured_timeout):
    +        # type: (float, int) -> None
    +        threading.Thread.__init__(self)
    +        self.waiting_time = waiting_time
    +        self.configured_timeout = configured_timeout
    +        self._stop_event = threading.Event()
    +
    +    def stop(self):
    +        # type: () -> None
    +        self._stop_event.set()
    +
    +    def run(self):
    +        # type: () -> None
    +
    +        self._stop_event.wait(self.waiting_time)
    +
    +        if self._stop_event.is_set():
    +            return
    +
    +        integer_configured_timeout = int(self.configured_timeout)
    +
    +        # Setting up the exact integer value of configured time(in seconds)
    +        if integer_configured_timeout < self.configured_timeout:
    +            integer_configured_timeout = integer_configured_timeout + 1
    +
    +        # Raising Exception after timeout duration is reached
    +        raise ServerlessTimeoutWarning(
    +            "WARNING : Function is expected to get timed out. Configured timeout duration = {} seconds.".format(
    +                integer_configured_timeout
    +            )
    +        )
    +
    +
    +def to_base64(original):
    +    # type: (str) -> Optional[str]
    +    """
    +    Convert a string to base64, via UTF-8. Returns None on invalid input.
    +    """
    +    base64_string = None
    +
    +    try:
    +        utf8_bytes = original.encode("UTF-8")
    +        base64_bytes = base64.b64encode(utf8_bytes)
    +        base64_string = base64_bytes.decode("UTF-8")
    +    except Exception as err:
    +        logger.warning("Unable to encode {orig} to base64:".format(orig=original), err)
    +
    +    return base64_string
    +
    +
    +def from_base64(base64_string):
    +    # type: (str) -> Optional[str]
    +    """
    +    Convert a string from base64, via UTF-8. Returns None on invalid input.
    +    """
    +    utf8_string = None
    +
    +    try:
    +        only_valid_chars = BASE64_ALPHABET.match(base64_string)
    +        assert only_valid_chars
    +
    +        base64_bytes = base64_string.encode("UTF-8")
    +        utf8_bytes = base64.b64decode(base64_bytes)
    +        utf8_string = utf8_bytes.decode("UTF-8")
    +    except Exception as err:
    +        logger.warning(
    +            "Unable to decode {b64} from base64:".format(b64=base64_string), err
    +        )
    +
    +    return utf8_string
    +
    +
    +Components = namedtuple("Components", ["scheme", "netloc", "path", "query", "fragment"])
    +
    +
    +def sanitize_url(url, remove_authority=True, remove_query_values=True, split=False):
    +    # type: (str, bool, bool, bool) -> Union[str, Components]
    +    """
    +    Removes the authority and query parameter values from a given URL.
    +    """
    +    parsed_url = urlsplit(url)
    +    query_params = parse_qs(parsed_url.query, keep_blank_values=True)
    +
    +    # strip username:password (netloc can be usr:pwd@example.com)
    +    if remove_authority:
    +        netloc_parts = parsed_url.netloc.split("@")
    +        if len(netloc_parts) > 1:
    +            netloc = "%s:%s@%s" % (
    +                SENSITIVE_DATA_SUBSTITUTE,
    +                SENSITIVE_DATA_SUBSTITUTE,
    +                netloc_parts[-1],
    +            )
    +        else:
    +            netloc = parsed_url.netloc
    +    else:
    +        netloc = parsed_url.netloc
    +
    +    # strip values from query string
    +    if remove_query_values:
    +        query_string = unquote(
    +            urlencode({key: SENSITIVE_DATA_SUBSTITUTE for key in query_params})
    +        )
    +    else:
    +        query_string = parsed_url.query
    +
    +    components = Components(
    +        scheme=parsed_url.scheme,
    +        netloc=netloc,
    +        query=query_string,
    +        path=parsed_url.path,
    +        fragment=parsed_url.fragment,
    +    )
    +
    +    if split:
    +        return components
    +    else:
    +        return urlunsplit(components)
    +
    +
    +ParsedUrl = namedtuple("ParsedUrl", ["url", "query", "fragment"])
    +
    +
    +def parse_url(url, sanitize=True):
    +    # type: (str, bool) -> ParsedUrl
    +    """
    +    Splits a URL into a url (including path), query and fragment. If sanitize is True, the query
    +    parameters will be sanitized to remove sensitive data. The autority (username and password)
    +    in the URL will always be removed.
    +    """
    +    parsed_url = sanitize_url(
    +        url, remove_authority=True, remove_query_values=sanitize, split=True
    +    )
    +
    +    base_url = urlunsplit(
    +        Components(
    +            scheme=parsed_url.scheme,  # type: ignore
    +            netloc=parsed_url.netloc,  # type: ignore
    +            query="",
    +            path=parsed_url.path,  # type: ignore
    +            fragment="",
    +        )
    +    )
    +
    +    return ParsedUrl(
    +        url=base_url,
    +        query=parsed_url.query,  # type: ignore
    +        fragment=parsed_url.fragment,  # type: ignore
    +    )
    +
    +
    +def is_valid_sample_rate(rate, source):
    +    # type: (Any, str) -> bool
    +    """
    +    Checks the given sample rate to make sure it is valid type and value (a
    +    boolean or a number between 0 and 1, inclusive).
    +    """
    +
    +    # both booleans and NaN are instances of Real, so a) checking for Real
    +    # checks for the possibility of a boolean also, and b) we have to check
    +    # separately for NaN and Decimal does not derive from Real so need to check that too
    +    if not isinstance(rate, (Real, Decimal)) or math.isnan(rate):
    +        logger.warning(
    +            "{source} Given sample rate is invalid. Sample rate must be a boolean or a number between 0 and 1. Got {rate} of type {type}.".format(
    +                source=source, rate=rate, type=type(rate)
    +            )
    +        )
    +        return False
    +
    +    # in case rate is a boolean, it will get cast to 1 if it's True and 0 if it's False
    +    rate = float(rate)
    +    if rate < 0 or rate > 1:
    +        logger.warning(
    +            "{source} Given sample rate is invalid. Sample rate must be between 0 and 1. Got {rate}.".format(
    +                source=source, rate=rate
    +            )
    +        )
    +        return False
    +
    +    return True
    +
    +
    +def match_regex_list(item, regex_list=None, substring_matching=False):
    +    # type: (str, Optional[List[str]], bool) -> bool
    +    if regex_list is None:
    +        return False
    +
    +    for item_matcher in regex_list:
    +        if not substring_matching and item_matcher[-1] != "$":
    +            item_matcher += "$"
    +
    +        matched = re.search(item_matcher, item)
    +        if matched:
    +            return True
    +
    +    return False
    +
    +
    +def is_sentry_url(client, url):
    +    # type: (sentry_sdk.client.BaseClient, str) -> bool
    +    """
    +    Determines whether the given URL matches the Sentry DSN.
    +    """
    +    return (
    +        client is not None
    +        and client.transport is not None
    +        and client.transport.parsed_dsn is not None
    +        and client.transport.parsed_dsn.netloc in url
    +    )
    +
    +
    +def _generate_installed_modules():
    +    # type: () -> Iterator[Tuple[str, str]]
    +    try:
    +        from importlib import metadata
    +
    +        yielded = set()
    +        for dist in metadata.distributions():
    +            name = dist.metadata["Name"]
    +            # `metadata` values may be `None`, see:
    +            # https://github.com/python/cpython/issues/91216
    +            # and
    +            # https://github.com/python/importlib_metadata/issues/371
    +            if name is not None:
    +                normalized_name = _normalize_module_name(name)
    +                if dist.version is not None and normalized_name not in yielded:
    +                    yield normalized_name, dist.version
    +                    yielded.add(normalized_name)
    +
    +    except ImportError:
    +        # < py3.8
    +        try:
    +            import pkg_resources
    +        except ImportError:
    +            return
    +
    +        for info in pkg_resources.working_set:
    +            yield _normalize_module_name(info.key), info.version
    +
    +
    +def _normalize_module_name(name):
    +    # type: (str) -> str
    +    return name.lower()
    +
    +
    +def _get_installed_modules():
    +    # type: () -> Dict[str, str]
    +    global _installed_modules
    +    if _installed_modules is None:
    +        _installed_modules = dict(_generate_installed_modules())
    +    return _installed_modules
    +
    +
    +def package_version(package):
    +    # type: (str) -> Optional[Tuple[int, ...]]
    +    installed_packages = _get_installed_modules()
    +    version = installed_packages.get(package)
    +    if version is None:
    +        return None
    +
    +    return parse_version(version)
    +
    +
    +def reraise(tp, value, tb=None):
    +    # type: (Optional[Type[BaseException]], Optional[BaseException], Optional[Any]) -> NoReturn
    +    assert value is not None
    +    if value.__traceback__ is not tb:
    +        raise value.with_traceback(tb)
    +    raise value
    +
    +
    +def _no_op(*_a, **_k):
    +    # type: (*Any, **Any) -> None
    +    """No-op function for ensure_integration_enabled."""
    +    pass
    +
    +
    +if TYPE_CHECKING:
    +
    +    @overload
    +    def ensure_integration_enabled(
    +        integration,  # type: type[sentry_sdk.integrations.Integration]
    +        original_function,  # type: Callable[P, R]
    +    ):
    +        # type: (...) -> Callable[[Callable[P, R]], Callable[P, R]]
    +        ...
    +
    +    @overload
    +    def ensure_integration_enabled(
    +        integration,  # type: type[sentry_sdk.integrations.Integration]
    +    ):
    +        # type: (...) -> Callable[[Callable[P, None]], Callable[P, None]]
    +        ...
    +
    +
    +def ensure_integration_enabled(
    +    integration,  # type: type[sentry_sdk.integrations.Integration]
    +    original_function=_no_op,  # type: Union[Callable[P, R], Callable[P, None]]
    +):
    +    # type: (...) -> Callable[[Callable[P, R]], Callable[P, R]]
    +    """
    +    Ensures a given integration is enabled prior to calling a Sentry-patched function.
    +
    +    The function takes as its parameters the integration that must be enabled and the original
    +    function that the SDK is patching. The function returns a function that takes the
    +    decorated (Sentry-patched) function as its parameter, and returns a function that, when
    +    called, checks whether the given integration is enabled. If the integration is enabled, the
    +    function calls the decorated, Sentry-patched function. If the integration is not enabled,
    +    the original function is called.
    +
    +    The function also takes care of preserving the original function's signature and docstring.
    +
    +    Example usage:
    +
    +    ```python
    +    @ensure_integration_enabled(MyIntegration, my_function)
    +    def patch_my_function():
    +        with sentry_sdk.start_transaction(...):
    +            return my_function()
    +    ```
    +    """
    +    if TYPE_CHECKING:
    +        # Type hint to ensure the default function has the right typing. The overloads
    +        # ensure the default _no_op function is only used when R is None.
    +        original_function = cast(Callable[P, R], original_function)
    +
    +    def patcher(sentry_patched_function):
    +        # type: (Callable[P, R]) -> Callable[P, R]
    +        def runner(*args: "P.args", **kwargs: "P.kwargs"):
    +            # type: (...) -> R
    +            if sentry_sdk.get_client().get_integration(integration) is None:
    +                return original_function(*args, **kwargs)
    +
    +            return sentry_patched_function(*args, **kwargs)
    +
    +        if original_function is _no_op:
    +            return wraps(sentry_patched_function)(runner)
    +
    +        return wraps(original_function)(runner)
    +
    +    return patcher
    +
    +
    +if PY37:
    +
    +    def nanosecond_time():
    +        # type: () -> int
    +        return time.perf_counter_ns()
    +
    +else:
    +
    +    def nanosecond_time():
    +        # type: () -> int
    +        return int(time.perf_counter() * 1e9)
    +
    +
    +def now():
    +    # type: () -> float
    +    return time.perf_counter()
    +
    +
    +try:
    +    from gevent import get_hub as get_gevent_hub
    +    from gevent.monkey import is_module_patched
    +except ImportError:
    +
    +    # it's not great that the signatures are different, get_hub can't return None
    +    # consider adding an if TYPE_CHECKING to change the signature to Optional[Hub]
    +    def get_gevent_hub():  # type: ignore[misc]
    +        # type: () -> Optional[Hub]
    +        return None
    +
    +    def is_module_patched(mod_name):
    +        # type: (str) -> bool
    +        # unable to import from gevent means no modules have been patched
    +        return False
    +
    +
    +def is_gevent():
    +    # type: () -> bool
    +    return is_module_patched("threading") or is_module_patched("_thread")
    +
    +
    +def get_current_thread_meta(thread=None):
    +    # type: (Optional[threading.Thread]) -> Tuple[Optional[int], Optional[str]]
    +    """
    +    Try to get the id of the current thread, with various fall backs.
    +    """
    +
    +    # if a thread is specified, that takes priority
    +    if thread is not None:
    +        try:
    +            thread_id = thread.ident
    +            thread_name = thread.name
    +            if thread_id is not None:
    +                return thread_id, thread_name
    +        except AttributeError:
    +            pass
    +
    +    # if the app is using gevent, we should look at the gevent hub first
    +    # as the id there differs from what the threading module reports
    +    if is_gevent():
    +        gevent_hub = get_gevent_hub()
    +        if gevent_hub is not None:
    +            try:
    +                # this is undocumented, so wrap it in try except to be safe
    +                return gevent_hub.thread_ident, None
    +            except AttributeError:
    +                pass
    +
    +    # use the current thread's id if possible
    +    try:
    +        thread = threading.current_thread()
    +        thread_id = thread.ident
    +        thread_name = thread.name
    +        if thread_id is not None:
    +            return thread_id, thread_name
    +    except AttributeError:
    +        pass
    +
    +    # if we can't get the current thread id, fall back to the main thread id
    +    try:
    +        thread = threading.main_thread()
    +        thread_id = thread.ident
    +        thread_name = thread.name
    +        if thread_id is not None:
    +            return thread_id, thread_name
    +    except AttributeError:
    +        pass
    +
    +    # we've tried everything, time to give up
    +    return None, None
    diff --git a/aws/lambda_demo/sentry_sdk/worker.py b/aws/lambda_demo/sentry_sdk/worker.py
    new file mode 100644
    index 000000000..b04ea582b
    --- /dev/null
    +++ b/aws/lambda_demo/sentry_sdk/worker.py
    @@ -0,0 +1,141 @@
    +import os
    +import threading
    +
    +from time import sleep, time
    +from sentry_sdk._queue import Queue, FullError
    +from sentry_sdk.utils import logger
    +from sentry_sdk.consts import DEFAULT_QUEUE_SIZE
    +
    +from typing import TYPE_CHECKING
    +
    +if TYPE_CHECKING:
    +    from typing import Any
    +    from typing import Optional
    +    from typing import Callable
    +
    +
    +_TERMINATOR = object()
    +
    +
    +class BackgroundWorker:
    +    def __init__(self, queue_size=DEFAULT_QUEUE_SIZE):
    +        # type: (int) -> None
    +        self._queue = Queue(queue_size)  # type: Queue
    +        self._lock = threading.Lock()
    +        self._thread = None  # type: Optional[threading.Thread]
    +        self._thread_for_pid = None  # type: Optional[int]
    +
    +    @property
    +    def is_alive(self):
    +        # type: () -> bool
    +        if self._thread_for_pid != os.getpid():
    +            return False
    +        if not self._thread:
    +            return False
    +        return self._thread.is_alive()
    +
    +    def _ensure_thread(self):
    +        # type: () -> None
    +        if not self.is_alive:
    +            self.start()
    +
    +    def _timed_queue_join(self, timeout):
    +        # type: (float) -> bool
    +        deadline = time() + timeout
    +        queue = self._queue
    +
    +        queue.all_tasks_done.acquire()
    +
    +        try:
    +            while queue.unfinished_tasks:
    +                delay = deadline - time()
    +                if delay <= 0:
    +                    return False
    +                queue.all_tasks_done.wait(timeout=delay)
    +
    +            return True
    +        finally:
    +            queue.all_tasks_done.release()
    +
    +    def start(self):
    +        # type: () -> None
    +        with self._lock:
    +            if not self.is_alive:
    +                self._thread = threading.Thread(
    +                    target=self._target, name="sentry-sdk.BackgroundWorker"
    +                )
    +                self._thread.daemon = True
    +                try:
    +                    self._thread.start()
    +                    self._thread_for_pid = os.getpid()
    +                except RuntimeError:
    +                    # At this point we can no longer start because the interpreter
    +                    # is already shutting down.  Sadly at this point we can no longer
    +                    # send out events.
    +                    self._thread = None
    +
    +    def kill(self):
    +        # type: () -> None
    +        """
    +        Kill worker thread. Returns immediately. Not useful for
    +        waiting on shutdown for events, use `flush` for that.
    +        """
    +        logger.debug("background worker got kill request")
    +        with self._lock:
    +            if self._thread:
    +                try:
    +                    self._queue.put_nowait(_TERMINATOR)
    +                except FullError:
    +                    logger.debug("background worker queue full, kill failed")
    +
    +                self._thread = None
    +                self._thread_for_pid = None
    +
    +    def flush(self, timeout, callback=None):
    +        # type: (float, Optional[Any]) -> None
    +        logger.debug("background worker got flush request")
    +        with self._lock:
    +            if self.is_alive and timeout > 0.0:
    +                self._wait_flush(timeout, callback)
    +        logger.debug("background worker flushed")
    +
    +    def full(self):
    +        # type: () -> bool
    +        return self._queue.full()
    +
    +    def _wait_flush(self, timeout, callback):
    +        # type: (float, Optional[Any]) -> None
    +        initial_timeout = min(0.1, timeout)
    +        if not self._timed_queue_join(initial_timeout):
    +            pending = self._queue.qsize() + 1
    +            logger.debug("%d event(s) pending on flush", pending)
    +            if callback is not None:
    +                callback(pending, timeout)
    +
    +            if not self._timed_queue_join(timeout - initial_timeout):
    +                pending = self._queue.qsize() + 1
    +                logger.error("flush timed out, dropped %s events", pending)
    +
    +    def submit(self, callback):
    +        # type: (Callable[[], None]) -> bool
    +        self._ensure_thread()
    +        try:
    +            self._queue.put_nowait(callback)
    +            return True
    +        except FullError:
    +            return False
    +
    +    def _target(self):
    +        # type: () -> None
    +        while True:
    +            callback = self._queue.get()
    +            try:
    +                if callback is _TERMINATOR:
    +                    break
    +                try:
    +                    callback()
    +                except Exception:
    +                    logger.error("Failed processing job", exc_info=True)
    +            finally:
    +                self._queue.task_done()
    +            sleep(0)
    diff --git a/aws/lambda_demo/urllib3-2.3.0.dist-info/INSTALLER b/aws/lambda_demo/urllib3-2.3.0.dist-info/INSTALLER
    new file mode 100644
    index 000000000..a1b589e38
    --- /dev/null
    +++ b/aws/lambda_demo/urllib3-2.3.0.dist-info/INSTALLER
    @@ -0,0 +1 @@
    +pip
    diff --git a/aws/lambda_demo/urllib3-2.3.0.dist-info/METADATA b/aws/lambda_demo/urllib3-2.3.0.dist-info/METADATA
    new file mode 100644
    index 000000000..d2064b925
    --- /dev/null
    +++ b/aws/lambda_demo/urllib3-2.3.0.dist-info/METADATA
    @@ -0,0 +1,154 @@
    +Metadata-Version: 2.4
    +Name: urllib3
    +Version: 2.3.0
    +Summary: HTTP library with thread-safe connection pooling, file post, and more.
    +Project-URL: Changelog, https://github.com/urllib3/urllib3/blob/main/CHANGES.rst
    +Project-URL: Documentation, https://urllib3.readthedocs.io
    +Project-URL: Code, https://github.com/urllib3/urllib3
    +Project-URL: Issue tracker, https://github.com/urllib3/urllib3/issues
    +Author-email: Andrey Petrov 
    +Maintainer-email: Seth Michael Larson , Quentin Pradet , Illia Volochii 
    +License-File: LICENSE.txt
    +Keywords: filepost,http,httplib,https,pooling,ssl,threadsafe,urllib
    +Classifier: Environment :: Web Environment
    +Classifier: Intended Audience :: Developers
    +Classifier: License :: OSI Approved :: MIT License
    +Classifier: Operating System :: OS Independent
    +Classifier: Programming Language :: Python
    +Classifier: Programming Language :: Python :: 3
    +Classifier: Programming Language :: Python :: 3 :: Only
    +Classifier: Programming Language :: Python :: 3.9
    +Classifier: Programming Language :: Python :: 3.10
    +Classifier: Programming Language :: Python :: 3.11
    +Classifier: Programming Language :: Python :: 3.12
    +Classifier: Programming Language :: Python :: 3.13
    +Classifier: Programming Language :: Python :: Implementation :: CPython
    +Classifier: Programming Language :: Python :: Implementation :: PyPy
    +Classifier: Topic :: Internet :: WWW/HTTP
    +Classifier: Topic :: Software Development :: Libraries
    +Requires-Python: >=3.9
    +Provides-Extra: brotli
    +Requires-Dist: brotli>=1.0.9; (platform_python_implementation == 'CPython') and extra == 'brotli'
    +Requires-Dist: brotlicffi>=0.8.0; (platform_python_implementation != 'CPython') and extra == 'brotli'
    +Provides-Extra: h2
    +Requires-Dist: h2<5,>=4; extra == 'h2'
    +Provides-Extra: socks
    +Requires-Dist: pysocks!=1.5.7,<2.0,>=1.5.6; extra == 'socks'
    +Provides-Extra: zstd
    +Requires-Dist: zstandard>=0.18.0; extra == 'zstd'
    +Description-Content-Type: text/markdown
    +
    +

    + +![urllib3](https://github.com/urllib3/urllib3/raw/main/docs/_static/banner_github.svg) + +

    + +

    + PyPI Version + Python Versions + Join our Discord + Coverage Status + Build Status on GitHub + Documentation Status
    + OpenSSF Scorecard + SLSA 3 + CII Best Practices +

    + +urllib3 is a powerful, *user-friendly* HTTP client for Python. Much of the +Python ecosystem already uses urllib3 and you should too. +urllib3 brings many critical features that are missing from the Python +standard libraries: + +- Thread safety. +- Connection pooling. +- Client-side SSL/TLS verification. +- File uploads with multipart encoding. +- Helpers for retrying requests and dealing with HTTP redirects. +- Support for gzip, deflate, brotli, and zstd encoding. +- Proxy support for HTTP and SOCKS. +- 100% test coverage. + +urllib3 is powerful and easy to use: + +```python3 +>>> import urllib3 +>>> resp = urllib3.request("GET", "http://httpbin.org/robots.txt") +>>> resp.status +200 +>>> resp.data +b"User-agent: *\nDisallow: /deny\n" +``` + +## Installing + +urllib3 can be installed with [pip](https://pip.pypa.io): + +```bash +$ python -m pip install urllib3 +``` + +Alternatively, you can grab the latest source code from [GitHub](https://github.com/urllib3/urllib3): + +```bash +$ git clone https://github.com/urllib3/urllib3.git +$ cd urllib3 +$ pip install . +``` + + +## Documentation + +urllib3 has usage and reference documentation at [urllib3.readthedocs.io](https://urllib3.readthedocs.io). + + +## Community + +urllib3 has a [community Discord channel](https://discord.gg/urllib3) for asking questions and +collaborating with other contributors. Drop by and say hello 👋 + + +## Contributing + +urllib3 happily accepts contributions. Please see our +[contributing documentation](https://urllib3.readthedocs.io/en/latest/contributing.html) +for some tips on getting started. + + +## Security Disclosures + +To report a security vulnerability, please use the +[Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure with maintainers. + + +## Maintainers + +- [@sethmlarson](https://github.com/sethmlarson) (Seth M. Larson) +- [@pquentin](https://github.com/pquentin) (Quentin Pradet) +- [@illia-v](https://github.com/illia-v) (Illia Volochii) +- [@theacodes](https://github.com/theacodes) (Thea Flowers) +- [@haikuginger](https://github.com/haikuginger) (Jess Shapiro) +- [@lukasa](https://github.com/lukasa) (Cory Benfield) +- [@sigmavirus24](https://github.com/sigmavirus24) (Ian Stapleton Cordasco) +- [@shazow](https://github.com/shazow) (Andrey Petrov) + +👋 + + +## Sponsorship + +If your company benefits from this library, please consider [sponsoring its +development](https://urllib3.readthedocs.io/en/latest/sponsors.html). + + +## For Enterprise + +Professional support for urllib3 is available as part of the [Tidelift +Subscription][1]. Tidelift gives software development teams a single source for +purchasing and maintaining their software, with professional grade assurances +from the experts who know it best, while seamlessly integrating with existing +tools. + +[1]: https://tidelift.com/subscription/pkg/pypi-urllib3?utm_source=pypi-urllib3&utm_medium=referral&utm_campaign=readme diff --git a/aws/lambda_demo/urllib3-2.3.0.dist-info/RECORD b/aws/lambda_demo/urllib3-2.3.0.dist-info/RECORD new file mode 100644 index 000000000..a37fe04ab --- /dev/null +++ b/aws/lambda_demo/urllib3-2.3.0.dist-info/RECORD @@ -0,0 +1,79 @@ +urllib3-2.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +urllib3-2.3.0.dist-info/METADATA,sha256=quSdNDoWoaG7idKpqr1_2N52fUBfxTtifpP3sofNwAY,6488 +urllib3-2.3.0.dist-info/RECORD,, +urllib3-2.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87 +urllib3-2.3.0.dist-info/licenses/LICENSE.txt,sha256=Ew46ZNX91dCWp1JpRjSn2d8oRGnehuVzIQAmgEHj1oY,1093 +urllib3/__init__.py,sha256=JMo1tg1nIV1AeJ2vENC_Txfl0e5h6Gzl9DGVk1rWRbo,6979 +urllib3/__pycache__/__init__.cpython-311.pyc,, +urllib3/__pycache__/_base_connection.cpython-311.pyc,, +urllib3/__pycache__/_collections.cpython-311.pyc,, +urllib3/__pycache__/_request_methods.cpython-311.pyc,, +urllib3/__pycache__/_version.cpython-311.pyc,, +urllib3/__pycache__/connection.cpython-311.pyc,, +urllib3/__pycache__/connectionpool.cpython-311.pyc,, +urllib3/__pycache__/exceptions.cpython-311.pyc,, +urllib3/__pycache__/fields.cpython-311.pyc,, +urllib3/__pycache__/filepost.cpython-311.pyc,, +urllib3/__pycache__/poolmanager.cpython-311.pyc,, +urllib3/__pycache__/response.cpython-311.pyc,, +urllib3/_base_connection.py,sha256=T1cwH3RhzsrBh6Bz3AOGVDboRsE7veijqZPXXQTR2Rg,5568 +urllib3/_collections.py,sha256=tM7c6J1iKtWZYV_QGYb8-r7Nr1524Dehnsa0Ufh6_mU,17295 +urllib3/_request_methods.py,sha256=gCeF85SO_UU4WoPwYHIoz_tw-eM_EVOkLFp8OFsC7DA,9931 +urllib3/_version.py,sha256=ChsIHG8bRc-eXUbXOgv4Fm4DstSKLq9FpsTAsaMeR08,411 +urllib3/connection.py,sha256=dsVIUaPrOdATuO9OGnF5GzMhlVKlAw-2qH9FWYuic4s,39875 +urllib3/connectionpool.py,sha256=ZEhudsa8BIubD2M0XoxBBsjxbsXwMgUScH7oQ9i-j1Y,43371 +urllib3/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +urllib3/contrib/__pycache__/__init__.cpython-311.pyc,, +urllib3/contrib/__pycache__/pyopenssl.cpython-311.pyc,, +urllib3/contrib/__pycache__/socks.cpython-311.pyc,, +urllib3/contrib/emscripten/__init__.py,sha256=u6KNgzjlFZbuAAXa_ybCR7gQ71VJESnF-IIdDA73brw,733 +urllib3/contrib/emscripten/__pycache__/__init__.cpython-311.pyc,, +urllib3/contrib/emscripten/__pycache__/connection.cpython-311.pyc,, +urllib3/contrib/emscripten/__pycache__/fetch.cpython-311.pyc,, +urllib3/contrib/emscripten/__pycache__/request.cpython-311.pyc,, +urllib3/contrib/emscripten/__pycache__/response.cpython-311.pyc,, +urllib3/contrib/emscripten/connection.py,sha256=j8DR_flE7hsoFhNfiqHLiaPaCsVbzG44jgahwvsQ52A,8771 +urllib3/contrib/emscripten/emscripten_fetch_worker.js,sha256=CDfYF_9CDobtx2lGidyJ1zjDEvwNT5F-dchmVWXDh0E,3655 +urllib3/contrib/emscripten/fetch.py,sha256=Li6sUnFuFog0fBiahk1Y0C0tdn5fxmj4ucDofPWFiXc,22867 +urllib3/contrib/emscripten/request.py,sha256=mL28szy1KvE3NJhWor5jNmarp8gwplDU-7gwGZY5g0Q,566 +urllib3/contrib/emscripten/response.py,sha256=sc0CJCEV_3t_HTadOmvWNuO-WkYbI1YfQWnBBmaeriE,9981 +urllib3/contrib/pyopenssl.py,sha256=uMNfy0e-wT-WNrTF-4oOQ17-I3w0NPrREm1t5AC9wxg,19398 +urllib3/contrib/socks.py,sha256=-iardc61GypsJzD6W6yuRS7KVCyfowcQrl_719H7lIM,7549 +urllib3/exceptions.py,sha256=VSkjQkvw3iolMKP_ZWfiiXumAj2VCvhmz2B3W-MP4BA,9633 +urllib3/fields.py,sha256=FCf7UULSkf10cuTRUWTQESzxgl1WT8e2aCy3kfyZins,10829 +urllib3/filepost.py,sha256=U8eNZ-mpKKHhrlbHEEiTxxgK16IejhEa7uz42yqA_dI,2388 +urllib3/http2/__init__.py,sha256=xzrASH7R5ANRkPJOot5lGnATOq3KKuyXzI42rcnwmqs,1741 +urllib3/http2/__pycache__/__init__.cpython-311.pyc,, +urllib3/http2/__pycache__/connection.cpython-311.pyc,, +urllib3/http2/__pycache__/probe.cpython-311.pyc,, +urllib3/http2/connection.py,sha256=GNlp9BjI3DmfSKe1W0b9IqRBeM8Q13xd2MA3ROcJ3dY,12668 +urllib3/http2/probe.py,sha256=nnAkqbhAakOiF75rz7W0udZ38Eeh_uD8fjV74N73FEI,3014 +urllib3/poolmanager.py,sha256=2_L2AjVDgoQ0qBmYbX9u9QqyU1u5J37zQbtv_-ueZQA,22913 +urllib3/py.typed,sha256=UaCuPFa3H8UAakbt-5G8SPacldTOGvJv18pPjUJ5gDY,93 +urllib3/response.py,sha256=PBW5ZnK3kYeq0fqeUYywyASKQWK7uRzSa-HgDBzZedU,45190 +urllib3/util/__init__.py,sha256=-qeS0QceivazvBEKDNFCAI-6ACcdDOE4TMvo7SLNlAQ,1001 +urllib3/util/__pycache__/__init__.cpython-311.pyc,, +urllib3/util/__pycache__/connection.cpython-311.pyc,, +urllib3/util/__pycache__/proxy.cpython-311.pyc,, +urllib3/util/__pycache__/request.cpython-311.pyc,, +urllib3/util/__pycache__/response.cpython-311.pyc,, +urllib3/util/__pycache__/retry.cpython-311.pyc,, +urllib3/util/__pycache__/ssl_.cpython-311.pyc,, +urllib3/util/__pycache__/ssl_match_hostname.cpython-311.pyc,, +urllib3/util/__pycache__/ssltransport.cpython-311.pyc,, +urllib3/util/__pycache__/timeout.cpython-311.pyc,, +urllib3/util/__pycache__/url.cpython-311.pyc,, +urllib3/util/__pycache__/util.cpython-311.pyc,, +urllib3/util/__pycache__/wait.cpython-311.pyc,, +urllib3/util/connection.py,sha256=JjO722lzHlzLXPTkr9ZWBdhseXnMVjMSb1DJLVrXSnQ,4444 +urllib3/util/proxy.py,sha256=seP8-Q5B6bB0dMtwPj-YcZZQ30vHuLqRu-tI0JZ2fzs,1148 +urllib3/util/request.py,sha256=qSwxEsJJ-9DUfFDEAZIgQe8sgyywKY0o3faH3ixi3i8,8218 +urllib3/util/response.py,sha256=vQE639uoEhj1vpjEdxu5lNIhJCSUZkd7pqllUI0BZOA,3374 +urllib3/util/retry.py,sha256=bj-2YUqblxLlv8THg5fxww-DM54XCbjgZXIQ71XioCY,18459 +urllib3/util/ssl_.py,sha256=CcYPnFKqJDn58Us2J5AFBQLMthIVVMjb69HwvdmPgoQ,18884 +urllib3/util/ssl_match_hostname.py,sha256=gaWqixoYtQ_GKO8fcRGFj3VXeMoqyxQQuUTPgWeiL_M,5812 +urllib3/util/ssltransport.py,sha256=Ez4O8pR_vT8dan_FvqBYS6dgDfBXEMfVfrzcdUoWfi4,8847 +urllib3/util/timeout.py,sha256=4eT1FVeZZU7h7mYD1Jq2OXNe4fxekdNvhoWUkZusRpA,10346 +urllib3/util/url.py,sha256=WRh-TMYXosmgp8m8lT4H5spoHw5yUjlcMCfU53AkoAs,15205 +urllib3/util/util.py,sha256=j3lbZK1jPyiwD34T8IgJzdWEZVT-4E-0vYIJi9UjeNA,1146 +urllib3/util/wait.py,sha256=_ph8IrUR3sqPqi0OopQgJUlH4wzkGeM5CiyA7XGGtmI,4423 diff --git a/aws/lambda_demo/urllib3-2.3.0.dist-info/WHEEL b/aws/lambda_demo/urllib3-2.3.0.dist-info/WHEEL new file mode 100644 index 000000000..12228d414 --- /dev/null +++ b/aws/lambda_demo/urllib3-2.3.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.27.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/aws/lambda_demo/urllib3-2.3.0.dist-info/licenses/LICENSE.txt b/aws/lambda_demo/urllib3-2.3.0.dist-info/licenses/LICENSE.txt new file mode 100644 index 000000000..e6183d027 --- /dev/null +++ b/aws/lambda_demo/urllib3-2.3.0.dist-info/licenses/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2008-2020 Andrey Petrov and contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/aws/lambda_demo/urllib3/__init__.py b/aws/lambda_demo/urllib3/__init__.py new file mode 100644 index 000000000..3fe782c8a --- /dev/null +++ b/aws/lambda_demo/urllib3/__init__.py @@ -0,0 +1,211 @@ +""" +Python HTTP library with thread-safe connection pooling, file post support, user friendly, and more +""" + +from __future__ import annotations + +# Set default logging handler to avoid "No handler found" warnings. +import logging +import sys +import typing +import warnings +from logging import NullHandler + +from . import exceptions +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from ._version import __version__ +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url +from .filepost import _TYPE_FIELDS, encode_multipart_formdata +from .poolmanager import PoolManager, ProxyManager, proxy_from_url +from .response import BaseHTTPResponse, HTTPResponse +from .util.request import make_headers +from .util.retry import Retry +from .util.timeout import Timeout + +# Ensure that Python is compiled with OpenSSL 1.1.1+ +# If the 'ssl' module isn't available at all that's +# fine, we only care if the module is available. +try: + import ssl +except ImportError: + pass +else: + if not ssl.OPENSSL_VERSION.startswith("OpenSSL "): # Defensive: + warnings.warn( + "urllib3 v2 only supports OpenSSL 1.1.1+, currently " + f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. " + "See: https://github.com/urllib3/urllib3/issues/3020", + exceptions.NotOpenSSLWarning, + ) + elif ssl.OPENSSL_VERSION_INFO < (1, 1, 1): # Defensive: + raise ImportError( + "urllib3 v2 only supports OpenSSL 1.1.1+, currently " + f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. " + "See: https://github.com/urllib3/urllib3/issues/2168" + ) + +__author__ = "Andrey Petrov (andrey.petrov@shazow.net)" +__license__ = "MIT" +__version__ = __version__ + +__all__ = ( + "HTTPConnectionPool", + "HTTPHeaderDict", + "HTTPSConnectionPool", + "PoolManager", + "ProxyManager", + "HTTPResponse", + "Retry", + "Timeout", + "add_stderr_logger", + "connection_from_url", + "disable_warnings", + "encode_multipart_formdata", + "make_headers", + "proxy_from_url", + "request", + "BaseHTTPResponse", +) + +logging.getLogger(__name__).addHandler(NullHandler()) + + +def add_stderr_logger( + level: int = logging.DEBUG, +) -> logging.StreamHandler[typing.TextIO]: + """ + Helper for quickly adding a StreamHandler to the logger. Useful for + debugging. + + Returns the handler after adding it. + """ + # This method needs to be in this __init__.py to get the __name__ correct + # even if urllib3 is vendored within another package. + logger = logging.getLogger(__name__) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) + logger.addHandler(handler) + logger.setLevel(level) + logger.debug("Added a stderr logging handler to logger: %s", __name__) + return handler + + +# ... Clean up. +del NullHandler + + +# All warning filters *must* be appended unless you're really certain that they +# shouldn't be: otherwise, it's very hard for users to use most Python +# mechanisms to silence them. +# SecurityWarning's always go off by default. +warnings.simplefilter("always", exceptions.SecurityWarning, append=True) +# InsecurePlatformWarning's don't vary between requests, so we keep it default. +warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True) + + +def disable_warnings(category: type[Warning] = exceptions.HTTPWarning) -> None: + """ + Helper for quickly disabling all urllib3 warnings. + """ + warnings.simplefilter("ignore", category) + + +_DEFAULT_POOL = PoolManager() + + +def request( + method: str, + url: str, + *, + body: _TYPE_BODY | None = None, + fields: _TYPE_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + preload_content: bool | None = True, + decode_content: bool | None = True, + redirect: bool | None = True, + retries: Retry | bool | int | None = None, + timeout: Timeout | float | int | None = 3, + json: typing.Any | None = None, +) -> BaseHTTPResponse: + """ + A convenience, top-level request method. It uses a module-global ``PoolManager`` instance. + Therefore, its side effects could be shared across dependencies relying on it. + To avoid side effects create a new ``PoolManager`` instance and use it instead. + The method does not accept low-level ``**urlopen_kw`` keyword arguments. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param fields: + Data to encode and send in the request body. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. + + :param bool preload_content: + If True, the response's body will be preloaded into memory. + + :param bool decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param redirect: + If True, automatically handle redirects (status codes 301, 302, + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param json: + Data to encode and send as JSON with UTF-encoded in the request body. + The ``"Content-Type"`` header will be set to ``"application/json"`` + unless specified otherwise. + """ + + return _DEFAULT_POOL.request( + method, + url, + body=body, + fields=fields, + headers=headers, + preload_content=preload_content, + decode_content=decode_content, + redirect=redirect, + retries=retries, + timeout=timeout, + json=json, + ) + + +if sys.platform == "emscripten": + from .contrib.emscripten import inject_into_urllib3 # noqa: 401 + + inject_into_urllib3() diff --git a/aws/lambda_demo/urllib3/_base_connection.py b/aws/lambda_demo/urllib3/_base_connection.py new file mode 100644 index 000000000..dc0f318c0 --- /dev/null +++ b/aws/lambda_demo/urllib3/_base_connection.py @@ -0,0 +1,165 @@ +from __future__ import annotations + +import typing + +from .util.connection import _TYPE_SOCKET_OPTIONS +from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT +from .util.url import Url + +_TYPE_BODY = typing.Union[bytes, typing.IO[typing.Any], typing.Iterable[bytes], str] + + +class ProxyConfig(typing.NamedTuple): + ssl_context: ssl.SSLContext | None + use_forwarding_for_https: bool + assert_hostname: None | str | typing.Literal[False] + assert_fingerprint: str | None + + +class _ResponseOptions(typing.NamedTuple): + # TODO: Remove this in favor of a better + # HTTP request/response lifecycle tracking. + request_method: str + request_url: str + preload_content: bool + decode_content: bool + enforce_content_length: bool + + +if typing.TYPE_CHECKING: + import ssl + from typing import Protocol + + from .response import BaseHTTPResponse + + class BaseHTTPConnection(Protocol): + default_port: typing.ClassVar[int] + default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] + + host: str + port: int + timeout: None | ( + float + ) # Instance doesn't store _DEFAULT_TIMEOUT, must be resolved. + blocksize: int + source_address: tuple[str, int] | None + socket_options: _TYPE_SOCKET_OPTIONS | None + + proxy: Url | None + proxy_config: ProxyConfig | None + + is_verified: bool + proxy_is_verified: bool | None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 8192, + socket_options: _TYPE_SOCKET_OPTIONS | None = ..., + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + ) -> None: ... + + def set_tunnel( + self, + host: str, + port: int | None = None, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: ... + + def connect(self) -> None: ... + + def request( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + # We know *at least* botocore is depending on the order of the + # first 3 parameters so to be safe we only mark the later ones + # as keyword-only to ensure we have space to extend. + *, + chunked: bool = False, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> None: ... + + def getresponse(self) -> BaseHTTPResponse: ... + + def close(self) -> None: ... + + @property + def is_closed(self) -> bool: + """Whether the connection either is brand new or has been previously closed. + If this property is True then both ``is_connected`` and ``has_connected_to_proxy`` + properties must be False. + """ + + @property + def is_connected(self) -> bool: + """Whether the connection is actively connected to any origin (proxy or target)""" + + @property + def has_connected_to_proxy(self) -> bool: + """Whether the connection has successfully connected to its proxy. + This returns False if no proxy is in use. Used to determine whether + errors are coming from the proxy layer or from tunnelling to the target origin. + """ + + class BaseHTTPSConnection(BaseHTTPConnection, Protocol): + default_port: typing.ClassVar[int] + default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] + + # Certificate verification methods + cert_reqs: int | str | None + assert_hostname: None | str | typing.Literal[False] + assert_fingerprint: str | None + ssl_context: ssl.SSLContext | None + + # Trusted CAs + ca_certs: str | None + ca_cert_dir: str | None + ca_cert_data: None | str | bytes + + # TLS version + ssl_minimum_version: int | None + ssl_maximum_version: int | None + ssl_version: int | str | None # Deprecated + + # Client certificates + cert_file: str | None + key_file: str | None + key_password: str | None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: _TYPE_SOCKET_OPTIONS | None = ..., + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + cert_reqs: int | str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + server_hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + ca_certs: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + ssl_version: int | str | None = None, # Deprecated + cert_file: str | None = None, + key_file: str | None = None, + key_password: str | None = None, + ) -> None: ... diff --git a/aws/lambda_demo/urllib3/_collections.py b/aws/lambda_demo/urllib3/_collections.py new file mode 100644 index 000000000..1b6c13642 --- /dev/null +++ b/aws/lambda_demo/urllib3/_collections.py @@ -0,0 +1,479 @@ +from __future__ import annotations + +import typing +from collections import OrderedDict +from enum import Enum, auto +from threading import RLock + +if typing.TYPE_CHECKING: + # We can only import Protocol if TYPE_CHECKING because it's a development + # dependency, and is not available at runtime. + from typing import Protocol + + from typing_extensions import Self + + class HasGettableStringKeys(Protocol): + def keys(self) -> typing.Iterator[str]: ... + + def __getitem__(self, key: str) -> str: ... + + +__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] + + +# Key type +_KT = typing.TypeVar("_KT") +# Value type +_VT = typing.TypeVar("_VT") +# Default type +_DT = typing.TypeVar("_DT") + +ValidHTTPHeaderSource = typing.Union[ + "HTTPHeaderDict", + typing.Mapping[str, str], + typing.Iterable[tuple[str, str]], + "HasGettableStringKeys", +] + + +class _Sentinel(Enum): + not_passed = auto() + + +def ensure_can_construct_http_header_dict( + potential: object, +) -> ValidHTTPHeaderSource | None: + if isinstance(potential, HTTPHeaderDict): + return potential + elif isinstance(potential, typing.Mapping): + # Full runtime checking of the contents of a Mapping is expensive, so for the + # purposes of typechecking, we assume that any Mapping is the right shape. + return typing.cast(typing.Mapping[str, str], potential) + elif isinstance(potential, typing.Iterable): + # Similarly to Mapping, full runtime checking of the contents of an Iterable is + # expensive, so for the purposes of typechecking, we assume that any Iterable + # is the right shape. + return typing.cast(typing.Iterable[tuple[str, str]], potential) + elif hasattr(potential, "keys") and hasattr(potential, "__getitem__"): + return typing.cast("HasGettableStringKeys", potential) + else: + return None + + +class RecentlyUsedContainer(typing.Generic[_KT, _VT], typing.MutableMapping[_KT, _VT]): + """ + Provides a thread-safe dict-like container which maintains up to + ``maxsize`` keys while throwing away the least-recently-used keys beyond + ``maxsize``. + + :param maxsize: + Maximum number of recent elements to retain. + + :param dispose_func: + Every time an item is evicted from the container, + ``dispose_func(value)`` is called. Callback which will get called + """ + + _container: typing.OrderedDict[_KT, _VT] + _maxsize: int + dispose_func: typing.Callable[[_VT], None] | None + lock: RLock + + def __init__( + self, + maxsize: int = 10, + dispose_func: typing.Callable[[_VT], None] | None = None, + ) -> None: + super().__init__() + self._maxsize = maxsize + self.dispose_func = dispose_func + self._container = OrderedDict() + self.lock = RLock() + + def __getitem__(self, key: _KT) -> _VT: + # Re-insert the item, moving it to the end of the eviction line. + with self.lock: + item = self._container.pop(key) + self._container[key] = item + return item + + def __setitem__(self, key: _KT, value: _VT) -> None: + evicted_item = None + with self.lock: + # Possibly evict the existing value of 'key' + try: + # If the key exists, we'll overwrite it, which won't change the + # size of the pool. Because accessing a key should move it to + # the end of the eviction line, we pop it out first. + evicted_item = key, self._container.pop(key) + self._container[key] = value + except KeyError: + # When the key does not exist, we insert the value first so that + # evicting works in all cases, including when self._maxsize is 0 + self._container[key] = value + if len(self._container) > self._maxsize: + # If we didn't evict an existing value, and we've hit our maximum + # size, then we have to evict the least recently used item from + # the beginning of the container. + evicted_item = self._container.popitem(last=False) + + # After releasing the lock on the pool, dispose of any evicted value. + if evicted_item is not None and self.dispose_func: + _, evicted_value = evicted_item + self.dispose_func(evicted_value) + + def __delitem__(self, key: _KT) -> None: + with self.lock: + value = self._container.pop(key) + + if self.dispose_func: + self.dispose_func(value) + + def __len__(self) -> int: + with self.lock: + return len(self._container) + + def __iter__(self) -> typing.NoReturn: + raise NotImplementedError( + "Iteration over this class is unlikely to be threadsafe." + ) + + def clear(self) -> None: + with self.lock: + # Copy pointers to all values, then wipe the mapping + values = list(self._container.values()) + self._container.clear() + + if self.dispose_func: + for value in values: + self.dispose_func(value) + + def keys(self) -> set[_KT]: # type: ignore[override] + with self.lock: + return set(self._container.keys()) + + +class HTTPHeaderDictItemView(set[tuple[str, str]]): + """ + HTTPHeaderDict is unusual for a Mapping[str, str] in that it has two modes of + address. + + If we directly try to get an item with a particular name, we will get a string + back that is the concatenated version of all the values: + + >>> d['X-Header-Name'] + 'Value1, Value2, Value3' + + However, if we iterate over an HTTPHeaderDict's items, we will optionally combine + these values based on whether combine=True was called when building up the dictionary + + >>> d = HTTPHeaderDict({"A": "1", "B": "foo"}) + >>> d.add("A", "2", combine=True) + >>> d.add("B", "bar") + >>> list(d.items()) + [ + ('A', '1, 2'), + ('B', 'foo'), + ('B', 'bar'), + ] + + This class conforms to the interface required by the MutableMapping ABC while + also giving us the nonstandard iteration behavior we want; items with duplicate + keys, ordered by time of first insertion. + """ + + _headers: HTTPHeaderDict + + def __init__(self, headers: HTTPHeaderDict) -> None: + self._headers = headers + + def __len__(self) -> int: + return len(list(self._headers.iteritems())) + + def __iter__(self) -> typing.Iterator[tuple[str, str]]: + return self._headers.iteritems() + + def __contains__(self, item: object) -> bool: + if isinstance(item, tuple) and len(item) == 2: + passed_key, passed_val = item + if isinstance(passed_key, str) and isinstance(passed_val, str): + return self._headers._has_value_for_header(passed_key, passed_val) + return False + + +class HTTPHeaderDict(typing.MutableMapping[str, str]): + """ + :param headers: + An iterable of field-value pairs. Must not contain multiple field names + when compared case-insensitively. + + :param kwargs: + Additional field-value pairs to pass in to ``dict.update``. + + A ``dict`` like container for storing HTTP Headers. + + Field names are stored and compared case-insensitively in compliance with + RFC 7230. Iteration provides the first case-sensitive key seen for each + case-insensitive pair. + + Using ``__setitem__`` syntax overwrites fields that compare equal + case-insensitively in order to maintain ``dict``'s api. For fields that + compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` + in a loop. + + If multiple fields that are equal case-insensitively are passed to the + constructor or ``.update``, the behavior is undefined and some will be + lost. + + >>> headers = HTTPHeaderDict() + >>> headers.add('Set-Cookie', 'foo=bar') + >>> headers.add('set-cookie', 'baz=quxx') + >>> headers['content-length'] = '7' + >>> headers['SET-cookie'] + 'foo=bar, baz=quxx' + >>> headers['Content-Length'] + '7' + """ + + _container: typing.MutableMapping[str, list[str]] + + def __init__(self, headers: ValidHTTPHeaderSource | None = None, **kwargs: str): + super().__init__() + self._container = {} # 'dict' is insert-ordered + if headers is not None: + if isinstance(headers, HTTPHeaderDict): + self._copy_from(headers) + else: + self.extend(headers) + if kwargs: + self.extend(kwargs) + + def __setitem__(self, key: str, val: str) -> None: + # avoid a bytes/str comparison by decoding before httplib + if isinstance(key, bytes): + key = key.decode("latin-1") + self._container[key.lower()] = [key, val] + + def __getitem__(self, key: str) -> str: + val = self._container[key.lower()] + return ", ".join(val[1:]) + + def __delitem__(self, key: str) -> None: + del self._container[key.lower()] + + def __contains__(self, key: object) -> bool: + if isinstance(key, str): + return key.lower() in self._container + return False + + def setdefault(self, key: str, default: str = "") -> str: + return super().setdefault(key, default) + + def __eq__(self, other: object) -> bool: + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return False + else: + other_as_http_header_dict = type(self)(maybe_constructable) + + return {k.lower(): v for k, v in self.itermerged()} == { + k.lower(): v for k, v in other_as_http_header_dict.itermerged() + } + + def __ne__(self, other: object) -> bool: + return not self.__eq__(other) + + def __len__(self) -> int: + return len(self._container) + + def __iter__(self) -> typing.Iterator[str]: + # Only provide the originally cased names + for vals in self._container.values(): + yield vals[0] + + def discard(self, key: str) -> None: + try: + del self[key] + except KeyError: + pass + + def add(self, key: str, val: str, *, combine: bool = False) -> None: + """Adds a (name, value) pair, doesn't overwrite the value if it already + exists. + + If this is called with combine=True, instead of adding a new header value + as a distinct item during iteration, this will instead append the value to + any existing header value with a comma. If no existing header value exists + for the key, then the value will simply be added, ignoring the combine parameter. + + >>> headers = HTTPHeaderDict(foo='bar') + >>> headers.add('Foo', 'baz') + >>> headers['foo'] + 'bar, baz' + >>> list(headers.items()) + [('foo', 'bar'), ('foo', 'baz')] + >>> headers.add('foo', 'quz', combine=True) + >>> list(headers.items()) + [('foo', 'bar, baz, quz')] + """ + # avoid a bytes/str comparison by decoding before httplib + if isinstance(key, bytes): + key = key.decode("latin-1") + key_lower = key.lower() + new_vals = [key, val] + # Keep the common case aka no item present as fast as possible + vals = self._container.setdefault(key_lower, new_vals) + if new_vals is not vals: + # if there are values here, then there is at least the initial + # key/value pair + assert len(vals) >= 2 + if combine: + vals[-1] = vals[-1] + ", " + val + else: + vals.append(val) + + def extend(self, *args: ValidHTTPHeaderSource, **kwargs: str) -> None: + """Generic import function for any type of header-like object. + Adapted version of MutableMapping.update in order to insert items + with self.add instead of self.__setitem__ + """ + if len(args) > 1: + raise TypeError( + f"extend() takes at most 1 positional arguments ({len(args)} given)" + ) + other = args[0] if len(args) >= 1 else () + + if isinstance(other, HTTPHeaderDict): + for key, val in other.iteritems(): + self.add(key, val) + elif isinstance(other, typing.Mapping): + for key, val in other.items(): + self.add(key, val) + elif isinstance(other, typing.Iterable): + other = typing.cast(typing.Iterable[tuple[str, str]], other) + for key, value in other: + self.add(key, value) + elif hasattr(other, "keys") and hasattr(other, "__getitem__"): + # THIS IS NOT A TYPESAFE BRANCH + # In this branch, the object has a `keys` attr but is not a Mapping or any of + # the other types indicated in the method signature. We do some stuff with + # it as though it partially implements the Mapping interface, but we're not + # doing that stuff safely AT ALL. + for key in other.keys(): + self.add(key, other[key]) + + for key, value in kwargs.items(): + self.add(key, value) + + @typing.overload + def getlist(self, key: str) -> list[str]: ... + + @typing.overload + def getlist(self, key: str, default: _DT) -> list[str] | _DT: ... + + def getlist( + self, key: str, default: _Sentinel | _DT = _Sentinel.not_passed + ) -> list[str] | _DT: + """Returns a list of all the values for the named field. Returns an + empty list if the key doesn't exist.""" + try: + vals = self._container[key.lower()] + except KeyError: + if default is _Sentinel.not_passed: + # _DT is unbound; empty list is instance of List[str] + return [] + # _DT is bound; default is instance of _DT + return default + else: + # _DT may or may not be bound; vals[1:] is instance of List[str], which + # meets our external interface requirement of `Union[List[str], _DT]`. + return vals[1:] + + def _prepare_for_method_change(self) -> Self: + """ + Remove content-specific header fields before changing the request + method to GET or HEAD according to RFC 9110, Section 15.4. + """ + content_specific_headers = [ + "Content-Encoding", + "Content-Language", + "Content-Location", + "Content-Type", + "Content-Length", + "Digest", + "Last-Modified", + ] + for header in content_specific_headers: + self.discard(header) + return self + + # Backwards compatibility for httplib + getheaders = getlist + getallmatchingheaders = getlist + iget = getlist + + # Backwards compatibility for http.cookiejar + get_all = getlist + + def __repr__(self) -> str: + return f"{type(self).__name__}({dict(self.itermerged())})" + + def _copy_from(self, other: HTTPHeaderDict) -> None: + for key in other: + val = other.getlist(key) + self._container[key.lower()] = [key, *val] + + def copy(self) -> Self: + clone = type(self)() + clone._copy_from(self) + return clone + + def iteritems(self) -> typing.Iterator[tuple[str, str]]: + """Iterate over all header lines, including duplicate ones.""" + for key in self: + vals = self._container[key.lower()] + for val in vals[1:]: + yield vals[0], val + + def itermerged(self) -> typing.Iterator[tuple[str, str]]: + """Iterate over all headers, merging duplicate ones together.""" + for key in self: + val = self._container[key.lower()] + yield val[0], ", ".join(val[1:]) + + def items(self) -> HTTPHeaderDictItemView: # type: ignore[override] + return HTTPHeaderDictItemView(self) + + def _has_value_for_header(self, header_name: str, potential_value: str) -> bool: + if header_name in self: + return potential_value in self._container[header_name.lower()][1:] + return False + + def __ior__(self, other: object) -> HTTPHeaderDict: + # Supports extending a header dict in-place using operator |= + # combining items with add instead of __setitem__ + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return NotImplemented + self.extend(maybe_constructable) + return self + + def __or__(self, other: object) -> Self: + # Supports merging header dicts using operator | + # combining items with add instead of __setitem__ + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return NotImplemented + result = self.copy() + result.extend(maybe_constructable) + return result + + def __ror__(self, other: object) -> Self: + # Supports merging header dicts using operator | when other is on left side + # combining items with add instead of __setitem__ + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return NotImplemented + result = type(self)(maybe_constructable) + result.extend(self) + return result diff --git a/aws/lambda_demo/urllib3/_request_methods.py b/aws/lambda_demo/urllib3/_request_methods.py new file mode 100644 index 000000000..297c271bf --- /dev/null +++ b/aws/lambda_demo/urllib3/_request_methods.py @@ -0,0 +1,278 @@ +from __future__ import annotations + +import json as _json +import typing +from urllib.parse import urlencode + +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from .filepost import _TYPE_FIELDS, encode_multipart_formdata +from .response import BaseHTTPResponse + +__all__ = ["RequestMethods"] + +_TYPE_ENCODE_URL_FIELDS = typing.Union[ + typing.Sequence[tuple[str, typing.Union[str, bytes]]], + typing.Mapping[str, typing.Union[str, bytes]], +] + + +class RequestMethods: + """ + Convenience mixin for classes who implement a :meth:`urlopen` method, such + as :class:`urllib3.HTTPConnectionPool` and + :class:`urllib3.PoolManager`. + + Provides behavior for making common types of HTTP request methods and + decides which type of request field encoding to use. + + Specifically, + + :meth:`.request_encode_url` is for sending requests whose fields are + encoded in the URL (such as GET, HEAD, DELETE). + + :meth:`.request_encode_body` is for sending requests whose fields are + encoded in the *body* of the request using multipart or www-form-urlencoded + (such as for POST, PUT, PATCH). + + :meth:`.request` is for making any kind of request, it will look up the + appropriate encoding format and use one of the above two methods to make + the request. + + Initializer parameters: + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + """ + + _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"} + + def __init__(self, headers: typing.Mapping[str, str] | None = None) -> None: + self.headers = headers or {} + + def urlopen( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + encode_multipart: bool = True, + multipart_boundary: str | None = None, + **kw: typing.Any, + ) -> BaseHTTPResponse: # Abstract + raise NotImplementedError( + "Classes extending RequestMethods must implement " + "their own ``urlopen`` method." + ) + + def request( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + fields: _TYPE_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + json: typing.Any | None = None, + **urlopen_kw: typing.Any, + ) -> BaseHTTPResponse: + """ + Make a request using :meth:`urlopen` with the appropriate encoding of + ``fields`` based on the ``method`` used. + + This is a convenience method that requires the least amount of manual + effort. It can be used in most situations, while still having the + option to drop down to more specific methods when necessary, such as + :meth:`request_encode_url`, :meth:`request_encode_body`, + or even the lowest level :meth:`urlopen`. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param fields: + Data to encode and send in the URL or request body, depending on ``method``. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param json: + Data to encode and send as JSON with UTF-encoded in the request body. + The ``"Content-Type"`` header will be set to ``"application/json"`` + unless specified otherwise. + """ + method = method.upper() + + if json is not None and body is not None: + raise TypeError( + "request got values for both 'body' and 'json' parameters which are mutually exclusive" + ) + + if json is not None: + if headers is None: + headers = self.headers + + if not ("content-type" in map(str.lower, headers.keys())): + headers = HTTPHeaderDict(headers) + headers["Content-Type"] = "application/json" + + body = _json.dumps(json, separators=(",", ":"), ensure_ascii=False).encode( + "utf-8" + ) + + if body is not None: + urlopen_kw["body"] = body + + if method in self._encode_url_methods: + return self.request_encode_url( + method, + url, + fields=fields, # type: ignore[arg-type] + headers=headers, + **urlopen_kw, + ) + else: + return self.request_encode_body( + method, url, fields=fields, headers=headers, **urlopen_kw + ) + + def request_encode_url( + self, + method: str, + url: str, + fields: _TYPE_ENCODE_URL_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + **urlopen_kw: str, + ) -> BaseHTTPResponse: + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the url. This is useful for request methods like GET, HEAD, DELETE, etc. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param fields: + Data to encode and send in the URL. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + """ + if headers is None: + headers = self.headers + + extra_kw: dict[str, typing.Any] = {"headers": headers} + extra_kw.update(urlopen_kw) + + if fields: + url += "?" + urlencode(fields) + + return self.urlopen(method, url, **extra_kw) + + def request_encode_body( + self, + method: str, + url: str, + fields: _TYPE_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + encode_multipart: bool = True, + multipart_boundary: str | None = None, + **urlopen_kw: str, + ) -> BaseHTTPResponse: + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the body. This is useful for request methods like POST, PUT, PATCH, etc. + + When ``encode_multipart=True`` (default), then + :func:`urllib3.encode_multipart_formdata` is used to encode + the payload with the appropriate content type. Otherwise + :func:`urllib.parse.urlencode` is used with the + 'application/x-www-form-urlencoded' content type. + + Multipart encoding must be used when posting files, and it's reasonably + safe to use it in other times too. However, it may break request + signing, such as with OAuth. + + Supports an optional ``fields`` parameter of key/value strings AND + key/filetuple. A filetuple is a (filename, data, MIME type) tuple where + the MIME type is optional. For example:: + + fields = { + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), + 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + } + + When uploading a file, providing a filename (the first parameter of the + tuple) is optional but recommended to best mimic behavior of browsers. + + Note that if ``headers`` are supplied, the 'Content-Type' header will + be overwritten because it depends on the dynamic random boundary string + which is used to compose the body of the request. The random boundary + string can be explicitly set with the ``multipart_boundary`` parameter. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param fields: + Data to encode and send in the request body. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param encode_multipart: + If True, encode the ``fields`` using the multipart/form-data MIME + format. + + :param multipart_boundary: + If not specified, then a random boundary will be generated using + :func:`urllib3.filepost.choose_boundary`. + """ + if headers is None: + headers = self.headers + + extra_kw: dict[str, typing.Any] = {"headers": HTTPHeaderDict(headers)} + body: bytes | str + + if fields: + if "body" in urlopen_kw: + raise TypeError( + "request got values for both 'fields' and 'body', can only specify one." + ) + + if encode_multipart: + body, content_type = encode_multipart_formdata( + fields, boundary=multipart_boundary + ) + else: + body, content_type = ( + urlencode(fields), # type: ignore[arg-type] + "application/x-www-form-urlencoded", + ) + + extra_kw["body"] = body + extra_kw["headers"].setdefault("Content-Type", content_type) + + extra_kw.update(urlopen_kw) + + return self.urlopen(method, url, **extra_kw) diff --git a/aws/lambda_demo/urllib3/_version.py b/aws/lambda_demo/urllib3/_version.py new file mode 100644 index 000000000..af6144bb2 --- /dev/null +++ b/aws/lambda_demo/urllib3/_version.py @@ -0,0 +1,16 @@ +# file generated by setuptools_scm +# don't change, don't track in version control +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple, Union + VERSION_TUPLE = Tuple[Union[int, str], ...] +else: + VERSION_TUPLE = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE + +__version__ = version = '2.3.0' +__version_tuple__ = version_tuple = (2, 3, 0) diff --git a/aws/lambda_demo/urllib3/connection.py b/aws/lambda_demo/urllib3/connection.py new file mode 100644 index 000000000..591ac407b --- /dev/null +++ b/aws/lambda_demo/urllib3/connection.py @@ -0,0 +1,1044 @@ +from __future__ import annotations + +import datetime +import http.client +import logging +import os +import re +import socket +import sys +import threading +import typing +import warnings +from http.client import HTTPConnection as _HTTPConnection +from http.client import HTTPException as HTTPException # noqa: F401 +from http.client import ResponseNotReady +from socket import timeout as SocketTimeout + +if typing.TYPE_CHECKING: + from .response import HTTPResponse + from .util.ssl_ import _TYPE_PEER_CERT_RET_DICT + from .util.ssltransport import SSLTransport + +from ._collections import HTTPHeaderDict +from .http2 import probe as http2_probe +from .util.response import assert_header_parsing +from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT, Timeout +from .util.util import to_str +from .util.wait import wait_for_read + +try: # Compiled with SSL? + import ssl + + BaseSSLError = ssl.SSLError +except (ImportError, AttributeError): + ssl = None # type: ignore[assignment] + + class BaseSSLError(BaseException): # type: ignore[no-redef] + pass + + +from ._base_connection import _TYPE_BODY +from ._base_connection import ProxyConfig as ProxyConfig +from ._base_connection import _ResponseOptions as _ResponseOptions +from ._version import __version__ +from .exceptions import ( + ConnectTimeoutError, + HeaderParsingError, + NameResolutionError, + NewConnectionError, + ProxyError, + SystemTimeWarning, +) +from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection, ssl_ +from .util.request import body_to_chunks +from .util.ssl_ import assert_fingerprint as _assert_fingerprint +from .util.ssl_ import ( + create_urllib3_context, + is_ipaddress, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .util.ssl_match_hostname import CertificateError, match_hostname +from .util.url import Url + +# Not a no-op, we're adding this to the namespace so it can be imported. +ConnectionError = ConnectionError +BrokenPipeError = BrokenPipeError + + +log = logging.getLogger(__name__) + +port_by_scheme = {"http": 80, "https": 443} + +# When it comes time to update this value as a part of regular maintenance +# (ie test_recent_date is failing) update it to ~6 months before the current date. +RECENT_DATE = datetime.date(2023, 6, 1) + +_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") + + +class HTTPConnection(_HTTPConnection): + """ + Based on :class:`http.client.HTTPConnection` but provides an extra constructor + backwards-compatibility layer between older and newer Pythons. + + Additional keyword parameters are used to configure attributes of the connection. + Accepted parameters include: + + - ``source_address``: Set the source address for the current connection. + - ``socket_options``: Set specific options on the underlying socket. If not specified, then + defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling + Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. + + For example, if you wish to enable TCP Keep Alive in addition to the defaults, + you might pass: + + .. code-block:: python + + HTTPConnection.default_socket_options + [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ] + + Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). + """ + + default_port: typing.ClassVar[int] = port_by_scheme["http"] # type: ignore[misc] + + #: Disable Nagle's algorithm by default. + #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` + default_socket_options: typing.ClassVar[connection._TYPE_SOCKET_OPTIONS] = [ + (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + ] + + #: Whether this connection verifies the host's certificate. + is_verified: bool = False + + #: Whether this proxy connection verified the proxy host's certificate. + # If no proxy is currently connected to the value will be ``None``. + proxy_is_verified: bool | None = None + + blocksize: int + source_address: tuple[str, int] | None + socket_options: connection._TYPE_SOCKET_OPTIONS | None + + _has_connected_to_proxy: bool + _response_options: _ResponseOptions | None + _tunnel_host: str | None + _tunnel_port: int | None + _tunnel_scheme: str | None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: None | ( + connection._TYPE_SOCKET_OPTIONS + ) = default_socket_options, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + ) -> None: + super().__init__( + host=host, + port=port, + timeout=Timeout.resolve_default_timeout(timeout), + source_address=source_address, + blocksize=blocksize, + ) + self.socket_options = socket_options + self.proxy = proxy + self.proxy_config = proxy_config + + self._has_connected_to_proxy = False + self._response_options = None + self._tunnel_host: str | None = None + self._tunnel_port: int | None = None + self._tunnel_scheme: str | None = None + + @property + def host(self) -> str: + """ + Getter method to remove any trailing dots that indicate the hostname is an FQDN. + + In general, SSL certificates don't include the trailing dot indicating a + fully-qualified domain name, and thus, they don't validate properly when + checked against a domain name that includes the dot. In addition, some + servers may not expect to receive the trailing dot when provided. + + However, the hostname with trailing dot is critical to DNS resolution; doing a + lookup with the trailing dot will properly only resolve the appropriate FQDN, + whereas a lookup without a trailing dot will search the system's search domain + list. Thus, it's important to keep the original host around for use only in + those cases where it's appropriate (i.e., when doing DNS lookup to establish the + actual TCP connection across which we're going to send HTTP requests). + """ + return self._dns_host.rstrip(".") + + @host.setter + def host(self, value: str) -> None: + """ + Setter for the `host` property. + + We assume that only urllib3 uses the _dns_host attribute; httplib itself + only uses `host`, and it seems reasonable that other libraries follow suit. + """ + self._dns_host = value + + def _new_conn(self) -> socket.socket: + """Establish a socket connection and set nodelay settings on it. + + :return: New socket connection. + """ + try: + sock = connection.create_connection( + (self._dns_host, self.port), + self.timeout, + source_address=self.source_address, + socket_options=self.socket_options, + ) + except socket.gaierror as e: + raise NameResolutionError(self.host, self, e) from e + except SocketTimeout as e: + raise ConnectTimeoutError( + self, + f"Connection to {self.host} timed out. (connect timeout={self.timeout})", + ) from e + + except OSError as e: + raise NewConnectionError( + self, f"Failed to establish a new connection: {e}" + ) from e + + sys.audit("http.client.connect", self, self.host, self.port) + + return sock + + def set_tunnel( + self, + host: str, + port: int | None = None, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: + if scheme not in ("http", "https"): + raise ValueError( + f"Invalid proxy scheme for tunneling: {scheme!r}, must be either 'http' or 'https'" + ) + super().set_tunnel(host, port=port, headers=headers) + self._tunnel_scheme = scheme + + if sys.version_info < (3, 11, 4): + + def _tunnel(self) -> None: + _MAXLINE = http.client._MAXLINE # type: ignore[attr-defined] + connect = b"CONNECT %s:%d HTTP/1.0\r\n" % ( # type: ignore[str-format] + self._tunnel_host.encode("ascii"), # type: ignore[union-attr] + self._tunnel_port, + ) + headers = [connect] + for header, value in self._tunnel_headers.items(): # type: ignore[attr-defined] + headers.append(f"{header}: {value}\r\n".encode("latin-1")) + headers.append(b"\r\n") + # Making a single send() call instead of one per line encourages + # the host OS to use a more optimal packet size instead of + # potentially emitting a series of small packets. + self.send(b"".join(headers)) + del headers + + response = self.response_class(self.sock, method=self._method) # type: ignore[attr-defined] + try: + (version, code, message) = response._read_status() # type: ignore[attr-defined] + + if code != http.HTTPStatus.OK: + self.close() + raise OSError(f"Tunnel connection failed: {code} {message.strip()}") + while True: + line = response.fp.readline(_MAXLINE + 1) + if len(line) > _MAXLINE: + raise http.client.LineTooLong("header line") + if not line: + # for sites which EOF without sending a trailer + break + if line in (b"\r\n", b"\n", b""): + break + + if self.debuglevel > 0: + print("header:", line.decode()) + finally: + response.close() + + def connect(self) -> None: + self.sock = self._new_conn() + if self._tunnel_host: + # If we're tunneling it means we're connected to our proxy. + self._has_connected_to_proxy = True + + # TODO: Fix tunnel so it doesn't depend on self.sock state. + self._tunnel() + + # If there's a proxy to be connected to we are fully connected. + # This is set twice (once above and here) due to forwarding proxies + # not using tunnelling. + self._has_connected_to_proxy = bool(self.proxy) + + if self._has_connected_to_proxy: + self.proxy_is_verified = False + + @property + def is_closed(self) -> bool: + return self.sock is None + + @property + def is_connected(self) -> bool: + if self.sock is None: + return False + return not wait_for_read(self.sock, timeout=0.0) + + @property + def has_connected_to_proxy(self) -> bool: + return self._has_connected_to_proxy + + @property + def proxy_is_forwarding(self) -> bool: + """ + Return True if a forwarding proxy is configured, else return False + """ + return bool(self.proxy) and self._tunnel_host is None + + @property + def proxy_is_tunneling(self) -> bool: + """ + Return True if a tunneling proxy is configured, else return False + """ + return self._tunnel_host is not None + + def close(self) -> None: + try: + super().close() + finally: + # Reset all stateful properties so connection + # can be re-used without leaking prior configs. + self.sock = None + self.is_verified = False + self.proxy_is_verified = None + self._has_connected_to_proxy = False + self._response_options = None + self._tunnel_host = None + self._tunnel_port = None + self._tunnel_scheme = None + + def putrequest( + self, + method: str, + url: str, + skip_host: bool = False, + skip_accept_encoding: bool = False, + ) -> None: + """""" + # Empty docstring because the indentation of CPython's implementation + # is broken but we don't want this method in our documentation. + match = _CONTAINS_CONTROL_CHAR_RE.search(method) + if match: + raise ValueError( + f"Method cannot contain non-token characters {method!r} (found at least {match.group()!r})" + ) + + return super().putrequest( + method, url, skip_host=skip_host, skip_accept_encoding=skip_accept_encoding + ) + + def putheader(self, header: str, *values: str) -> None: # type: ignore[override] + """""" + if not any(isinstance(v, str) and v == SKIP_HEADER for v in values): + super().putheader(header, *values) + elif to_str(header.lower()) not in SKIPPABLE_HEADERS: + skippable_headers = "', '".join( + [str.title(header) for header in sorted(SKIPPABLE_HEADERS)] + ) + raise ValueError( + f"urllib3.util.SKIP_HEADER only supports '{skippable_headers}'" + ) + + # `request` method's signature intentionally violates LSP. + # urllib3's API is different from `http.client.HTTPConnection` and the subclassing is only incidental. + def request( # type: ignore[override] + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + *, + chunked: bool = False, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> None: + # Update the inner socket's timeout value to send the request. + # This only triggers if the connection is re-used. + if self.sock is not None: + self.sock.settimeout(self.timeout) + + # Store these values to be fed into the HTTPResponse + # object later. TODO: Remove this in favor of a real + # HTTP lifecycle mechanism. + + # We have to store these before we call .request() + # because sometimes we can still salvage a response + # off the wire even if we aren't able to completely + # send the request body. + self._response_options = _ResponseOptions( + request_method=method, + request_url=url, + preload_content=preload_content, + decode_content=decode_content, + enforce_content_length=enforce_content_length, + ) + + if headers is None: + headers = {} + header_keys = frozenset(to_str(k.lower()) for k in headers) + skip_accept_encoding = "accept-encoding" in header_keys + skip_host = "host" in header_keys + self.putrequest( + method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host + ) + + # Transform the body into an iterable of sendall()-able chunks + # and detect if an explicit Content-Length is doable. + chunks_and_cl = body_to_chunks(body, method=method, blocksize=self.blocksize) + chunks = chunks_and_cl.chunks + content_length = chunks_and_cl.content_length + + # When chunked is explicit set to 'True' we respect that. + if chunked: + if "transfer-encoding" not in header_keys: + self.putheader("Transfer-Encoding", "chunked") + else: + # Detect whether a framing mechanism is already in use. If so + # we respect that value, otherwise we pick chunked vs content-length + # depending on the type of 'body'. + if "content-length" in header_keys: + chunked = False + elif "transfer-encoding" in header_keys: + chunked = True + + # Otherwise we go off the recommendation of 'body_to_chunks()'. + else: + chunked = False + if content_length is None: + if chunks is not None: + chunked = True + self.putheader("Transfer-Encoding", "chunked") + else: + self.putheader("Content-Length", str(content_length)) + + # Now that framing headers are out of the way we send all the other headers. + if "user-agent" not in header_keys: + self.putheader("User-Agent", _get_default_user_agent()) + for header, value in headers.items(): + self.putheader(header, value) + self.endheaders() + + # If we're given a body we start sending that in chunks. + if chunks is not None: + for chunk in chunks: + # Sending empty chunks isn't allowed for TE: chunked + # as it indicates the end of the body. + if not chunk: + continue + if isinstance(chunk, str): + chunk = chunk.encode("utf-8") + if chunked: + self.send(b"%x\r\n%b\r\n" % (len(chunk), chunk)) + else: + self.send(chunk) + + # Regardless of whether we have a body or not, if we're in + # chunked mode we want to send an explicit empty chunk. + if chunked: + self.send(b"0\r\n\r\n") + + def request_chunked( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + ) -> None: + """ + Alternative to the common request method, which sends the + body with chunked encoding and not as one block + """ + warnings.warn( + "HTTPConnection.request_chunked() is deprecated and will be removed " + "in urllib3 v2.1.0. Instead use HTTPConnection.request(..., chunked=True).", + category=DeprecationWarning, + stacklevel=2, + ) + self.request(method, url, body=body, headers=headers, chunked=True) + + def getresponse( # type: ignore[override] + self, + ) -> HTTPResponse: + """ + Get the response from the server. + + If the HTTPConnection is in the correct state, returns an instance of HTTPResponse or of whatever object is returned by the response_class variable. + + If a request has not been sent or if a previous response has not be handled, ResponseNotReady is raised. If the HTTP response indicates that the connection should be closed, then it will be closed before the response is returned. When the connection is closed, the underlying socket is closed. + """ + # Raise the same error as http.client.HTTPConnection + if self._response_options is None: + raise ResponseNotReady() + + # Reset this attribute for being used again. + resp_options = self._response_options + self._response_options = None + + # Since the connection's timeout value may have been updated + # we need to set the timeout on the socket. + self.sock.settimeout(self.timeout) + + # This is needed here to avoid circular import errors + from .response import HTTPResponse + + # Save a reference to the shutdown function before ownership is passed + # to httplib_response + # TODO should we implement it everywhere? + _shutdown = getattr(self.sock, "shutdown", None) + + # Get the response from http.client.HTTPConnection + httplib_response = super().getresponse() + + try: + assert_header_parsing(httplib_response.msg) + except (HeaderParsingError, TypeError) as hpe: + log.warning( + "Failed to parse headers (url=%s): %s", + _url_from_connection(self, resp_options.request_url), + hpe, + exc_info=True, + ) + + headers = HTTPHeaderDict(httplib_response.msg.items()) + + response = HTTPResponse( + body=httplib_response, + headers=headers, + status=httplib_response.status, + version=httplib_response.version, + version_string=getattr(self, "_http_vsn_str", "HTTP/?"), + reason=httplib_response.reason, + preload_content=resp_options.preload_content, + decode_content=resp_options.decode_content, + original_response=httplib_response, + enforce_content_length=resp_options.enforce_content_length, + request_method=resp_options.request_method, + request_url=resp_options.request_url, + sock_shutdown=_shutdown, + ) + return response + + +class HTTPSConnection(HTTPConnection): + """ + Many of the parameters to this constructor are passed to the underlying SSL + socket by means of :py:func:`urllib3.util.ssl_wrap_socket`. + """ + + default_port = port_by_scheme["https"] # type: ignore[misc] + + cert_reqs: int | str | None = None + ca_certs: str | None = None + ca_cert_dir: str | None = None + ca_cert_data: None | str | bytes = None + ssl_version: int | str | None = None + ssl_minimum_version: int | None = None + ssl_maximum_version: int | None = None + assert_fingerprint: str | None = None + _connect_callback: typing.Callable[..., None] | None = None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: None | ( + connection._TYPE_SOCKET_OPTIONS + ) = HTTPConnection.default_socket_options, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + cert_reqs: int | str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + server_hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + ca_certs: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + ssl_version: int | str | None = None, # Deprecated + cert_file: str | None = None, + key_file: str | None = None, + key_password: str | None = None, + ) -> None: + super().__init__( + host, + port=port, + timeout=timeout, + source_address=source_address, + blocksize=blocksize, + socket_options=socket_options, + proxy=proxy, + proxy_config=proxy_config, + ) + + self.key_file = key_file + self.cert_file = cert_file + self.key_password = key_password + self.ssl_context = ssl_context + self.server_hostname = server_hostname + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ssl_version = ssl_version + self.ssl_minimum_version = ssl_minimum_version + self.ssl_maximum_version = ssl_maximum_version + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data + + # cert_reqs depends on ssl_context so calculate last. + if cert_reqs is None: + if self.ssl_context is not None: + cert_reqs = self.ssl_context.verify_mode + else: + cert_reqs = resolve_cert_reqs(None) + self.cert_reqs = cert_reqs + self._connect_callback = None + + def set_cert( + self, + key_file: str | None = None, + cert_file: str | None = None, + cert_reqs: int | str | None = None, + key_password: str | None = None, + ca_certs: str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ) -> None: + """ + This method should only be called once, before the connection is used. + """ + warnings.warn( + "HTTPSConnection.set_cert() is deprecated and will be removed " + "in urllib3 v2.1.0. Instead provide the parameters to the " + "HTTPSConnection constructor.", + category=DeprecationWarning, + stacklevel=2, + ) + + # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also + # have an SSLContext object in which case we'll use its verify_mode. + if cert_reqs is None: + if self.ssl_context is not None: + cert_reqs = self.ssl_context.verify_mode + else: + cert_reqs = resolve_cert_reqs(None) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data + + def connect(self) -> None: + # Today we don't need to be doing this step before the /actual/ socket + # connection, however in the future we'll need to decide whether to + # create a new socket or re-use an existing "shared" socket as a part + # of the HTTP/2 handshake dance. + if self._tunnel_host is not None and self._tunnel_port is not None: + probe_http2_host = self._tunnel_host + probe_http2_port = self._tunnel_port + else: + probe_http2_host = self.host + probe_http2_port = self.port + + # Check if the target origin supports HTTP/2. + # If the value comes back as 'None' it means that the current thread + # is probing for HTTP/2 support. Otherwise, we're waiting for another + # probe to complete, or we get a value right away. + target_supports_http2: bool | None + if "h2" in ssl_.ALPN_PROTOCOLS: + target_supports_http2 = http2_probe.acquire_and_get( + host=probe_http2_host, port=probe_http2_port + ) + else: + # If HTTP/2 isn't going to be offered it doesn't matter if + # the target supports HTTP/2. Don't want to make a probe. + target_supports_http2 = False + + if self._connect_callback is not None: + self._connect_callback( + "before connect", + thread_id=threading.get_ident(), + target_supports_http2=target_supports_http2, + ) + + try: + sock: socket.socket | ssl.SSLSocket + self.sock = sock = self._new_conn() + server_hostname: str = self.host + tls_in_tls = False + + # Do we need to establish a tunnel? + if self.proxy_is_tunneling: + # We're tunneling to an HTTPS origin so need to do TLS-in-TLS. + if self._tunnel_scheme == "https": + # _connect_tls_proxy will verify and assign proxy_is_verified + self.sock = sock = self._connect_tls_proxy(self.host, sock) + tls_in_tls = True + elif self._tunnel_scheme == "http": + self.proxy_is_verified = False + + # If we're tunneling it means we're connected to our proxy. + self._has_connected_to_proxy = True + + self._tunnel() + # Override the host with the one we're requesting data from. + server_hostname = typing.cast(str, self._tunnel_host) + + if self.server_hostname is not None: + server_hostname = self.server_hostname + + is_time_off = datetime.date.today() < RECENT_DATE + if is_time_off: + warnings.warn( + ( + f"System time is way off (before {RECENT_DATE}). This will probably " + "lead to SSL verification errors" + ), + SystemTimeWarning, + ) + + # Remove trailing '.' from fqdn hostnames to allow certificate validation + server_hostname_rm_dot = server_hostname.rstrip(".") + + sock_and_verified = _ssl_wrap_socket_and_match_hostname( + sock=sock, + cert_reqs=self.cert_reqs, + ssl_version=self.ssl_version, + ssl_minimum_version=self.ssl_minimum_version, + ssl_maximum_version=self.ssl_maximum_version, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + server_hostname=server_hostname_rm_dot, + ssl_context=self.ssl_context, + tls_in_tls=tls_in_tls, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ) + self.sock = sock_and_verified.socket + + # If an error occurs during connection/handshake we may need to release + # our lock so another connection can probe the origin. + except BaseException: + if self._connect_callback is not None: + self._connect_callback( + "after connect failure", + thread_id=threading.get_ident(), + target_supports_http2=target_supports_http2, + ) + + if target_supports_http2 is None: + http2_probe.set_and_release( + host=probe_http2_host, port=probe_http2_port, supports_http2=None + ) + raise + + # If this connection doesn't know if the origin supports HTTP/2 + # we report back to the HTTP/2 probe our result. + if target_supports_http2 is None: + supports_http2 = sock_and_verified.socket.selected_alpn_protocol() == "h2" + http2_probe.set_and_release( + host=probe_http2_host, + port=probe_http2_port, + supports_http2=supports_http2, + ) + + # Forwarding proxies can never have a verified target since + # the proxy is the one doing the verification. Should instead + # use a CONNECT tunnel in order to verify the target. + # See: https://github.com/urllib3/urllib3/issues/3267. + if self.proxy_is_forwarding: + self.is_verified = False + else: + self.is_verified = sock_and_verified.is_verified + + # If there's a proxy to be connected to we are fully connected. + # This is set twice (once above and here) due to forwarding proxies + # not using tunnelling. + self._has_connected_to_proxy = bool(self.proxy) + + # Set `self.proxy_is_verified` unless it's already set while + # establishing a tunnel. + if self._has_connected_to_proxy and self.proxy_is_verified is None: + self.proxy_is_verified = sock_and_verified.is_verified + + def _connect_tls_proxy(self, hostname: str, sock: socket.socket) -> ssl.SSLSocket: + """ + Establish a TLS connection to the proxy using the provided SSL context. + """ + # `_connect_tls_proxy` is called when self._tunnel_host is truthy. + proxy_config = typing.cast(ProxyConfig, self.proxy_config) + ssl_context = proxy_config.ssl_context + sock_and_verified = _ssl_wrap_socket_and_match_hostname( + sock, + cert_reqs=self.cert_reqs, + ssl_version=self.ssl_version, + ssl_minimum_version=self.ssl_minimum_version, + ssl_maximum_version=self.ssl_maximum_version, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + server_hostname=hostname, + ssl_context=ssl_context, + assert_hostname=proxy_config.assert_hostname, + assert_fingerprint=proxy_config.assert_fingerprint, + # Features that aren't implemented for proxies yet: + cert_file=None, + key_file=None, + key_password=None, + tls_in_tls=False, + ) + self.proxy_is_verified = sock_and_verified.is_verified + return sock_and_verified.socket # type: ignore[return-value] + + +class _WrappedAndVerifiedSocket(typing.NamedTuple): + """ + Wrapped socket and whether the connection is + verified after the TLS handshake + """ + + socket: ssl.SSLSocket | SSLTransport + is_verified: bool + + +def _ssl_wrap_socket_and_match_hostname( + sock: socket.socket, + *, + cert_reqs: None | str | int, + ssl_version: None | str | int, + ssl_minimum_version: int | None, + ssl_maximum_version: int | None, + cert_file: str | None, + key_file: str | None, + key_password: str | None, + ca_certs: str | None, + ca_cert_dir: str | None, + ca_cert_data: None | str | bytes, + assert_hostname: None | str | typing.Literal[False], + assert_fingerprint: str | None, + server_hostname: str | None, + ssl_context: ssl.SSLContext | None, + tls_in_tls: bool = False, +) -> _WrappedAndVerifiedSocket: + """Logic for constructing an SSLContext from all TLS parameters, passing + that down into ssl_wrap_socket, and then doing certificate verification + either via hostname or fingerprint. This function exists to guarantee + that both proxies and targets have the same behavior when connecting via TLS. + """ + default_ssl_context = False + if ssl_context is None: + default_ssl_context = True + context = create_urllib3_context( + ssl_version=resolve_ssl_version(ssl_version), + ssl_minimum_version=ssl_minimum_version, + ssl_maximum_version=ssl_maximum_version, + cert_reqs=resolve_cert_reqs(cert_reqs), + ) + else: + context = ssl_context + + context.verify_mode = resolve_cert_reqs(cert_reqs) + + # In some cases, we want to verify hostnames ourselves + if ( + # `ssl` can't verify fingerprints or alternate hostnames + assert_fingerprint + or assert_hostname + # assert_hostname can be set to False to disable hostname checking + or assert_hostname is False + # We still support OpenSSL 1.0.2, which prevents us from verifying + # hostnames easily: https://github.com/pyca/pyopenssl/pull/933 + or ssl_.IS_PYOPENSSL + or not ssl_.HAS_NEVER_CHECK_COMMON_NAME + ): + context.check_hostname = False + + # Try to load OS default certs if none are given. We need to do the hasattr() check + # for custom pyOpenSSL SSLContext objects because they don't support + # load_default_certs(). + if ( + not ca_certs + and not ca_cert_dir + and not ca_cert_data + and default_ssl_context + and hasattr(context, "load_default_certs") + ): + context.load_default_certs() + + # Ensure that IPv6 addresses are in the proper format and don't have a + # scope ID. Python's SSL module fails to recognize scoped IPv6 addresses + # and interprets them as DNS hostnames. + if server_hostname is not None: + normalized = server_hostname.strip("[]") + if "%" in normalized: + normalized = normalized[: normalized.rfind("%")] + if is_ipaddress(normalized): + server_hostname = normalized + + ssl_sock = ssl_wrap_socket( + sock=sock, + keyfile=key_file, + certfile=cert_file, + key_password=key_password, + ca_certs=ca_certs, + ca_cert_dir=ca_cert_dir, + ca_cert_data=ca_cert_data, + server_hostname=server_hostname, + ssl_context=context, + tls_in_tls=tls_in_tls, + ) + + try: + if assert_fingerprint: + _assert_fingerprint( + ssl_sock.getpeercert(binary_form=True), assert_fingerprint + ) + elif ( + context.verify_mode != ssl.CERT_NONE + and not context.check_hostname + and assert_hostname is not False + ): + cert: _TYPE_PEER_CERT_RET_DICT = ssl_sock.getpeercert() # type: ignore[assignment] + + # Need to signal to our match_hostname whether to use 'commonName' or not. + # If we're using our own constructed SSLContext we explicitly set 'False' + # because PyPy hard-codes 'True' from SSLContext.hostname_checks_common_name. + if default_ssl_context: + hostname_checks_common_name = False + else: + hostname_checks_common_name = ( + getattr(context, "hostname_checks_common_name", False) or False + ) + + _match_hostname( + cert, + assert_hostname or server_hostname, # type: ignore[arg-type] + hostname_checks_common_name, + ) + + return _WrappedAndVerifiedSocket( + socket=ssl_sock, + is_verified=context.verify_mode == ssl.CERT_REQUIRED + or bool(assert_fingerprint), + ) + except BaseException: + ssl_sock.close() + raise + + +def _match_hostname( + cert: _TYPE_PEER_CERT_RET_DICT | None, + asserted_hostname: str, + hostname_checks_common_name: bool = False, +) -> None: + # Our upstream implementation of ssl.match_hostname() + # only applies this normalization to IP addresses so it doesn't + # match DNS SANs so we do the same thing! + stripped_hostname = asserted_hostname.strip("[]") + if is_ipaddress(stripped_hostname): + asserted_hostname = stripped_hostname + + try: + match_hostname(cert, asserted_hostname, hostname_checks_common_name) + except CertificateError as e: + log.warning( + "Certificate did not match expected hostname: %s. Certificate: %s", + asserted_hostname, + cert, + ) + # Add cert to exception and reraise so client code can inspect + # the cert when catching the exception, if they want to + e._peer_cert = cert # type: ignore[attr-defined] + raise + + +def _wrap_proxy_error(err: Exception, proxy_scheme: str | None) -> ProxyError: + # Look for the phrase 'wrong version number', if found + # then we should warn the user that we're very sure that + # this proxy is HTTP-only and they have a configuration issue. + error_normalized = " ".join(re.split("[^a-z]", str(err).lower())) + is_likely_http_proxy = ( + "wrong version number" in error_normalized + or "unknown protocol" in error_normalized + or "record layer failure" in error_normalized + ) + http_proxy_warning = ( + ". Your proxy appears to only use HTTP and not HTTPS, " + "try changing your proxy URL to be HTTP. See: " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" + "#https-proxy-error-http-proxy" + ) + new_err = ProxyError( + f"Unable to connect to proxy" + f"{http_proxy_warning if is_likely_http_proxy and proxy_scheme == 'https' else ''}", + err, + ) + new_err.__cause__ = err + return new_err + + +def _get_default_user_agent() -> str: + return f"python-urllib3/{__version__}" + + +class DummyConnection: + """Used to detect a failed ConnectionCls import.""" + + +if not ssl: + HTTPSConnection = DummyConnection # type: ignore[misc, assignment] # noqa: F811 + + +VerifiedHTTPSConnection = HTTPSConnection + + +def _url_from_connection( + conn: HTTPConnection | HTTPSConnection, path: str | None = None +) -> str: + """Returns the URL from a given connection. This is mainly used for testing and logging.""" + + scheme = "https" if isinstance(conn, HTTPSConnection) else "http" + + return Url(scheme=scheme, host=conn.host, port=conn.port, path=path).url diff --git a/aws/lambda_demo/urllib3/connectionpool.py b/aws/lambda_demo/urllib3/connectionpool.py new file mode 100644 index 000000000..3a0685b4c --- /dev/null +++ b/aws/lambda_demo/urllib3/connectionpool.py @@ -0,0 +1,1178 @@ +from __future__ import annotations + +import errno +import logging +import queue +import sys +import typing +import warnings +import weakref +from socket import timeout as SocketTimeout +from types import TracebackType + +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from ._request_methods import RequestMethods +from .connection import ( + BaseSSLError, + BrokenPipeError, + DummyConnection, + HTTPConnection, + HTTPException, + HTTPSConnection, + ProxyConfig, + _wrap_proxy_error, +) +from .connection import port_by_scheme as port_by_scheme +from .exceptions import ( + ClosedPoolError, + EmptyPoolError, + FullPoolError, + HostChangedError, + InsecureRequestWarning, + LocationValueError, + MaxRetryError, + NewConnectionError, + ProtocolError, + ProxyError, + ReadTimeoutError, + SSLError, + TimeoutError, +) +from .response import BaseHTTPResponse +from .util.connection import is_connection_dropped +from .util.proxy import connection_requires_http_tunnel +from .util.request import _TYPE_BODY_POSITION, set_file_position +from .util.retry import Retry +from .util.ssl_match_hostname import CertificateError +from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_DEFAULT, Timeout +from .util.url import Url, _encode_target +from .util.url import _normalize_host as normalize_host +from .util.url import parse_url +from .util.util import to_str + +if typing.TYPE_CHECKING: + import ssl + + from typing_extensions import Self + + from ._base_connection import BaseHTTPConnection, BaseHTTPSConnection + +log = logging.getLogger(__name__) + +_TYPE_TIMEOUT = typing.Union[Timeout, float, _TYPE_DEFAULT, None] + + +# Pool objects +class ConnectionPool: + """ + Base class for all connection pools, such as + :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. + + .. note:: + ConnectionPool.urlopen() does not normalize or percent-encode target URIs + which is useful if your target server doesn't support percent-encoded + target URIs. + """ + + scheme: str | None = None + QueueCls = queue.LifoQueue + + def __init__(self, host: str, port: int | None = None) -> None: + if not host: + raise LocationValueError("No host specified.") + + self.host = _normalize_host(host, scheme=self.scheme) + self.port = port + + # This property uses 'normalize_host()' (not '_normalize_host()') + # to avoid removing square braces around IPv6 addresses. + # This value is sent to `HTTPConnection.set_tunnel()` if called + # because square braces are required for HTTP CONNECT tunneling. + self._tunnel_host = normalize_host(host, scheme=self.scheme).lower() + + def __str__(self) -> str: + return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})" + + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> typing.Literal[False]: + self.close() + # Return False to re-raise any potential exceptions + return False + + def close(self) -> None: + """ + Close all pooled connections and disable the pool. + """ + + +# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 +_blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK} + + +class HTTPConnectionPool(ConnectionPool, RequestMethods): + """ + Thread-safe connection pool for one host. + + :param host: + Host used for this HTTP Connection (e.g. "localhost"), passed into + :class:`http.client.HTTPConnection`. + + :param port: + Port used for this HTTP Connection (None is equivalent to 80), passed + into :class:`http.client.HTTPConnection`. + + :param timeout: + Socket timeout in seconds for each individual connection. This can + be a float or integer, which sets the timeout for the HTTP request, + or an instance of :class:`urllib3.util.Timeout` which gives you more + fine-grained control over request timeouts. After the constructor has + been parsed, this is always a `urllib3.util.Timeout` object. + + :param maxsize: + Number of connections to save that can be reused. More than 1 is useful + in multithreaded situations. If ``block`` is set to False, more + connections will be created but they will not be saved once they've + been used. + + :param block: + If set to True, no more than ``maxsize`` connections will be used at + a time. When no free connections are available, the call will block + until a connection has been released. This is a useful side effect for + particular multithreaded situations where one does not want to use more + than maxsize connections per host to prevent flooding. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param retries: + Retry configuration to use by default with requests in this pool. + + :param _proxy: + Parsed proxy URL, should not be used directly, instead, see + :class:`urllib3.ProxyManager` + + :param _proxy_headers: + A dictionary with proxy headers, should not be used directly, + instead, see :class:`urllib3.ProxyManager` + + :param \\**conn_kw: + Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, + :class:`urllib3.connection.HTTPSConnection` instances. + """ + + scheme = "http" + ConnectionCls: type[BaseHTTPConnection] | type[BaseHTTPSConnection] = HTTPConnection + + def __init__( + self, + host: str, + port: int | None = None, + timeout: _TYPE_TIMEOUT | None = _DEFAULT_TIMEOUT, + maxsize: int = 1, + block: bool = False, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | bool | int | None = None, + _proxy: Url | None = None, + _proxy_headers: typing.Mapping[str, str] | None = None, + _proxy_config: ProxyConfig | None = None, + **conn_kw: typing.Any, + ): + ConnectionPool.__init__(self, host, port) + RequestMethods.__init__(self, headers) + + if not isinstance(timeout, Timeout): + timeout = Timeout.from_float(timeout) + + if retries is None: + retries = Retry.DEFAULT + + self.timeout = timeout + self.retries = retries + + self.pool: queue.LifoQueue[typing.Any] | None = self.QueueCls(maxsize) + self.block = block + + self.proxy = _proxy + self.proxy_headers = _proxy_headers or {} + self.proxy_config = _proxy_config + + # Fill the queue up so that doing get() on it will block properly + for _ in range(maxsize): + self.pool.put(None) + + # These are mostly for testing and debugging purposes. + self.num_connections = 0 + self.num_requests = 0 + self.conn_kw = conn_kw + + if self.proxy: + # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. + # We cannot know if the user has added default socket options, so we cannot replace the + # list. + self.conn_kw.setdefault("socket_options", []) + + self.conn_kw["proxy"] = self.proxy + self.conn_kw["proxy_config"] = self.proxy_config + + # Do not pass 'self' as callback to 'finalize'. + # Then the 'finalize' would keep an endless living (leak) to self. + # By just passing a reference to the pool allows the garbage collector + # to free self if nobody else has a reference to it. + pool = self.pool + + # Close all the HTTPConnections in the pool before the + # HTTPConnectionPool object is garbage collected. + weakref.finalize(self, _close_pool_connections, pool) + + def _new_conn(self) -> BaseHTTPConnection: + """ + Return a fresh :class:`HTTPConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTP connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "80", + ) + + conn = self.ConnectionCls( + host=self.host, + port=self.port, + timeout=self.timeout.connect_timeout, + **self.conn_kw, + ) + return conn + + def _get_conn(self, timeout: float | None = None) -> BaseHTTPConnection: + """ + Get a connection. Will return a pooled connection if one is available. + + If no connections are available and :prop:`.block` is ``False``, then a + fresh connection is returned. + + :param timeout: + Seconds to wait before giving up and raising + :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and + :prop:`.block` is ``True``. + """ + conn = None + + if self.pool is None: + raise ClosedPoolError(self, "Pool is closed.") + + try: + conn = self.pool.get(block=self.block, timeout=timeout) + + except AttributeError: # self.pool is None + raise ClosedPoolError(self, "Pool is closed.") from None # Defensive: + + except queue.Empty: + if self.block: + raise EmptyPoolError( + self, + "Pool is empty and a new connection can't be opened due to blocking mode.", + ) from None + pass # Oh well, we'll create a new connection then + + # If this is a persistent connection, check if it got disconnected + if conn and is_connection_dropped(conn): + log.debug("Resetting dropped connection: %s", self.host) + conn.close() + + return conn or self._new_conn() + + def _put_conn(self, conn: BaseHTTPConnection | None) -> None: + """ + Put a connection back into the pool. + + :param conn: + Connection object for the current host and port as returned by + :meth:`._new_conn` or :meth:`._get_conn`. + + If the pool is already full, the connection is closed and discarded + because we exceeded maxsize. If connections are discarded frequently, + then maxsize should be increased. + + If the pool is closed, then the connection will be closed and discarded. + """ + if self.pool is not None: + try: + self.pool.put(conn, block=False) + return # Everything is dandy, done. + except AttributeError: + # self.pool is None. + pass + except queue.Full: + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + if self.block: + # This should never happen if you got the conn from self._get_conn + raise FullPoolError( + self, + "Pool reached maximum size and no more connections are allowed.", + ) from None + + log.warning( + "Connection pool is full, discarding connection: %s. Connection pool size: %s", + self.host, + self.pool.qsize(), + ) + + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + def _validate_conn(self, conn: BaseHTTPConnection) -> None: + """ + Called right before a request is made, after the socket is created. + """ + + def _prepare_proxy(self, conn: BaseHTTPConnection) -> None: + # Nothing to do for HTTP connections. + pass + + def _get_timeout(self, timeout: _TYPE_TIMEOUT) -> Timeout: + """Helper that always returns a :class:`urllib3.util.Timeout`""" + if timeout is _DEFAULT_TIMEOUT: + return self.timeout.clone() + + if isinstance(timeout, Timeout): + return timeout.clone() + else: + # User passed us an int/float. This is for backwards compatibility, + # can be removed later + return Timeout.from_float(timeout) + + def _raise_timeout( + self, + err: BaseSSLError | OSError | SocketTimeout, + url: str, + timeout_value: _TYPE_TIMEOUT | None, + ) -> None: + """Is the error actually a timeout? Will raise a ReadTimeout or pass""" + + if isinstance(err, SocketTimeout): + raise ReadTimeoutError( + self, url, f"Read timed out. (read timeout={timeout_value})" + ) from err + + # See the above comment about EAGAIN in Python 3. + if hasattr(err, "errno") and err.errno in _blocking_errnos: + raise ReadTimeoutError( + self, url, f"Read timed out. (read timeout={timeout_value})" + ) from err + + def _make_request( + self, + conn: BaseHTTPConnection, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | None = None, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + chunked: bool = False, + response_conn: BaseHTTPConnection | None = None, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> BaseHTTPResponse: + """ + Perform a request on a given urllib connection object taken from our + pool. + + :param conn: + a connection from one of our connection pools + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + Pass ``None`` to retry until you receive a response. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param chunked: + If True, urllib3 will send the body using chunked transfer + encoding. Otherwise, urllib3 will send the body using the standard + content-length form. Defaults to False. + + :param response_conn: + Set this to ``None`` if you will handle releasing the connection or + set the connection to have the response release it. + + :param preload_content: + If True, the response's body will be preloaded during construction. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param enforce_content_length: + Enforce content length checking. Body returned by server must match + value of Content-Length header, if present. Otherwise, raise error. + """ + self.num_requests += 1 + + timeout_obj = self._get_timeout(timeout) + timeout_obj.start_connect() + conn.timeout = Timeout.resolve_default_timeout(timeout_obj.connect_timeout) + + try: + # Trigger any extra validation we need to do. + try: + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise + + # _validate_conn() starts the connection to an HTTPS proxy + # so we need to wrap errors with 'ProxyError' here too. + except ( + OSError, + NewConnectionError, + TimeoutError, + BaseSSLError, + CertificateError, + SSLError, + ) as e: + new_e: Exception = e + if isinstance(e, (BaseSSLError, CertificateError)): + new_e = SSLError(e) + # If the connection didn't successfully connect to it's proxy + # then there + if isinstance( + new_e, (OSError, NewConnectionError, TimeoutError, SSLError) + ) and (conn and conn.proxy and not conn.has_connected_to_proxy): + new_e = _wrap_proxy_error(new_e, conn.proxy.scheme) + raise new_e + + # conn.request() calls http.client.*.request, not the method in + # urllib3.request. It also calls makefile (recv) on the socket. + try: + conn.request( + method, + url, + body=body, + headers=headers, + chunked=chunked, + preload_content=preload_content, + decode_content=decode_content, + enforce_content_length=enforce_content_length, + ) + + # We are swallowing BrokenPipeError (errno.EPIPE) since the server is + # legitimately able to close the connection after sending a valid response. + # With this behaviour, the received response is still readable. + except BrokenPipeError: + pass + except OSError as e: + # MacOS/Linux + # EPROTOTYPE and ECONNRESET are needed on macOS + # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ + # Condition changed later to emit ECONNRESET instead of only EPROTOTYPE. + if e.errno != errno.EPROTOTYPE and e.errno != errno.ECONNRESET: + raise + + # Reset the timeout for the recv() on the socket + read_timeout = timeout_obj.read_timeout + + if not conn.is_closed: + # In Python 3 socket.py will catch EAGAIN and return None when you + # try and read into the file pointer created by http.client, which + # instead raises a BadStatusLine exception. Instead of catching + # the exception and assuming all BadStatusLine exceptions are read + # timeouts, check for a zero timeout before making the request. + if read_timeout == 0: + raise ReadTimeoutError( + self, url, f"Read timed out. (read timeout={read_timeout})" + ) + conn.timeout = read_timeout + + # Receive the response from the server + try: + response = conn.getresponse() + except (BaseSSLError, OSError) as e: + self._raise_timeout(err=e, url=url, timeout_value=read_timeout) + raise + + # Set properties that are used by the pooling layer. + response.retries = retries + response._connection = response_conn # type: ignore[attr-defined] + response._pool = self # type: ignore[attr-defined] + + log.debug( + '%s://%s:%s "%s %s %s" %s %s', + self.scheme, + self.host, + self.port, + method, + url, + response.version_string, + response.status, + response.length_remaining, + ) + + return response + + def close(self) -> None: + """ + Close all pooled connections and disable the pool. + """ + if self.pool is None: + return + # Disable access to the pool + old_pool, self.pool = self.pool, None + + # Close all the HTTPConnections in the pool. + _close_pool_connections(old_pool) + + def is_same_host(self, url: str) -> bool: + """ + Check if the given ``url`` is a member of the same host as this + connection pool. + """ + if url.startswith("/"): + return True + + # TODO: Add optional support for socket.gethostbyname checking. + scheme, _, host, port, *_ = parse_url(url) + scheme = scheme or "http" + if host is not None: + host = _normalize_host(host, scheme=scheme) + + # Use explicit default port for comparison when none is given + if self.port and not port: + port = port_by_scheme.get(scheme) + elif not self.port and port == port_by_scheme.get(scheme): + port = None + + return (scheme, host, port) == (self.scheme, self.host, self.port) + + def urlopen( # type: ignore[override] + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | bool | int | None = None, + redirect: bool = True, + assert_same_host: bool = True, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + pool_timeout: int | None = None, + release_conn: bool | None = None, + chunked: bool = False, + body_pos: _TYPE_BODY_POSITION | None = None, + preload_content: bool = True, + decode_content: bool = True, + **response_kw: typing.Any, + ) -> BaseHTTPResponse: + """ + Get a connection from the pool and perform an HTTP request. This is the + lowest level call for making a request, so you'll need to specify all + the raw details. + + .. note:: + + More commonly, it's appropriate to use a convenience method + such as :meth:`request`. + + .. note:: + + `release_conn` will only behave as expected if + `preload_content=False` because we want to make + `preload_content=False` the default behaviour someday soon without + breaking backwards compatibility. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param redirect: + If True, automatically handle redirects (status codes 301, 302, + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. + + :param assert_same_host: + If ``True``, will make sure that the host of the pool requests is + consistent else will raise HostChangedError. When ``False``, you can + use the pool on an HTTP proxy and request foreign hosts. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param pool_timeout: + If set and the pool is set to block=True, then this method will + block for ``pool_timeout`` seconds and raise EmptyPoolError if no + connection is available within the time period. + + :param bool preload_content: + If True, the response's body will be preloaded into memory. + + :param bool decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param release_conn: + If False, then the urlopen call will not release the connection + back into the pool once a response is received (but will release if + you read the entire contents of the response such as when + `preload_content=True`). This is useful if you're not preloading + the response's content immediately. You will need to call + ``r.release_conn()`` on the response ``r`` to return the connection + back into the pool. If None, it takes the value of ``preload_content`` + which defaults to ``True``. + + :param bool chunked: + If True, urllib3 will send the body using chunked transfer + encoding. Otherwise, urllib3 will send the body using the standard + content-length form. Defaults to False. + + :param int body_pos: + Position to seek to in file-like body in the event of a retry or + redirect. Typically this won't need to be set because urllib3 will + auto-populate the value when needed. + """ + parsed_url = parse_url(url) + destination_scheme = parsed_url.scheme + + if headers is None: + headers = self.headers + + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if release_conn is None: + release_conn = preload_content + + # Check host + if assert_same_host and not self.is_same_host(url): + raise HostChangedError(self, url, retries) + + # Ensure that the URL we're connecting to is properly encoded + if url.startswith("/"): + url = to_str(_encode_target(url)) + else: + url = to_str(parsed_url.url) + + conn = None + + # Track whether `conn` needs to be released before + # returning/raising/recursing. Update this variable if necessary, and + # leave `release_conn` constant throughout the function. That way, if + # the function recurses, the original value of `release_conn` will be + # passed down into the recursive call, and its value will be respected. + # + # See issue #651 [1] for details. + # + # [1] + release_this_conn = release_conn + + http_tunnel_required = connection_requires_http_tunnel( + self.proxy, self.proxy_config, destination_scheme + ) + + # Merge the proxy headers. Only done when not using HTTP CONNECT. We + # have to copy the headers dict so we can safely change it without those + # changes being reflected in anyone else's copy. + if not http_tunnel_required: + headers = headers.copy() # type: ignore[attr-defined] + headers.update(self.proxy_headers) # type: ignore[union-attr] + + # Must keep the exception bound to a separate variable or else Python 3 + # complains about UnboundLocalError. + err = None + + # Keep track of whether we cleanly exited the except block. This + # ensures we do proper cleanup in finally. + clean_exit = False + + # Rewind body position, if needed. Record current position + # for future rewinds in the event of a redirect/retry. + body_pos = set_file_position(body, body_pos) + + try: + # Request a connection from the queue. + timeout_obj = self._get_timeout(timeout) + conn = self._get_conn(timeout=pool_timeout) + + conn.timeout = timeout_obj.connect_timeout # type: ignore[assignment] + + # Is this a closed/new connection that requires CONNECT tunnelling? + if self.proxy is not None and http_tunnel_required and conn.is_closed: + try: + self._prepare_proxy(conn) + except (BaseSSLError, OSError, SocketTimeout) as e: + self._raise_timeout( + err=e, url=self.proxy.url, timeout_value=conn.timeout + ) + raise + + # If we're going to release the connection in ``finally:``, then + # the response doesn't need to know about the connection. Otherwise + # it will also try to release it and we'll have a double-release + # mess. + response_conn = conn if not release_conn else None + + # Make the request on the HTTPConnection object + response = self._make_request( + conn, + method, + url, + timeout=timeout_obj, + body=body, + headers=headers, + chunked=chunked, + retries=retries, + response_conn=response_conn, + preload_content=preload_content, + decode_content=decode_content, + **response_kw, + ) + + # Everything went great! + clean_exit = True + + except EmptyPoolError: + # Didn't get a connection from the pool, no need to clean up + clean_exit = True + release_this_conn = False + raise + + except ( + TimeoutError, + HTTPException, + OSError, + ProtocolError, + BaseSSLError, + SSLError, + CertificateError, + ProxyError, + ) as e: + # Discard the connection for these exceptions. It will be + # replaced during the next _get_conn() call. + clean_exit = False + new_e: Exception = e + if isinstance(e, (BaseSSLError, CertificateError)): + new_e = SSLError(e) + if isinstance( + new_e, + ( + OSError, + NewConnectionError, + TimeoutError, + SSLError, + HTTPException, + ), + ) and (conn and conn.proxy and not conn.has_connected_to_proxy): + new_e = _wrap_proxy_error(new_e, conn.proxy.scheme) + elif isinstance(new_e, (OSError, HTTPException)): + new_e = ProtocolError("Connection aborted.", new_e) + + retries = retries.increment( + method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2] + ) + retries.sleep() + + # Keep track of the error for the retry warning. + err = e + + finally: + if not clean_exit: + # We hit some kind of exception, handled or otherwise. We need + # to throw the connection away unless explicitly told not to. + # Close the connection, set the variable to None, and make sure + # we put the None back in the pool to avoid leaking it. + if conn: + conn.close() + conn = None + release_this_conn = True + + if release_this_conn: + # Put the connection back to be reused. If the connection is + # expired then it will be None, which will get replaced with a + # fresh connection during _get_conn. + self._put_conn(conn) + + if not conn: + # Try again + log.warning( + "Retrying (%r) after connection broken by '%r': %s", retries, err, url + ) + return self.urlopen( + method, + url, + body, + headers, + retries, + redirect, + assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + preload_content=preload_content, + decode_content=decode_content, + **response_kw, + ) + + # Handle redirect? + redirect_location = redirect and response.get_redirect_location() + if redirect_location: + if response.status == 303: + # Change the method according to RFC 9110, Section 15.4.4. + method = "GET" + # And lose the body not to transfer anything sensitive. + body = None + headers = HTTPHeaderDict(headers)._prepare_for_method_change() + + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_redirect: + response.drain_conn() + raise + return response + + response.drain_conn() + retries.sleep_for_retry(response) + log.debug("Redirecting %s -> %s", url, redirect_location) + return self.urlopen( + method, + redirect_location, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + preload_content=preload_content, + decode_content=decode_content, + **response_kw, + ) + + # Check if we should retry the HTTP response. + has_retry_after = bool(response.headers.get("Retry-After")) + if retries.is_retry(method, response.status, has_retry_after): + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_status: + response.drain_conn() + raise + return response + + response.drain_conn() + retries.sleep(response) + log.debug("Retry: %s", url) + return self.urlopen( + method, + url, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + preload_content=preload_content, + decode_content=decode_content, + **response_kw, + ) + + return response + + +class HTTPSConnectionPool(HTTPConnectionPool): + """ + Same as :class:`.HTTPConnectionPool`, but HTTPS. + + :class:`.HTTPSConnection` uses one of ``assert_fingerprint``, + ``assert_hostname`` and ``host`` in this order to verify connections. + If ``assert_hostname`` is False, no verification is done. + + The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, + ``ca_cert_dir``, ``ssl_version``, ``key_password`` are only used if :mod:`ssl` + is available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade + the connection socket into an SSL socket. + """ + + scheme = "https" + ConnectionCls: type[BaseHTTPSConnection] = HTTPSConnection + + def __init__( + self, + host: str, + port: int | None = None, + timeout: _TYPE_TIMEOUT | None = _DEFAULT_TIMEOUT, + maxsize: int = 1, + block: bool = False, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | bool | int | None = None, + _proxy: Url | None = None, + _proxy_headers: typing.Mapping[str, str] | None = None, + key_file: str | None = None, + cert_file: str | None = None, + cert_reqs: int | str | None = None, + key_password: str | None = None, + ca_certs: str | None = None, + ssl_version: int | str | None = None, + ssl_minimum_version: ssl.TLSVersion | None = None, + ssl_maximum_version: ssl.TLSVersion | None = None, + assert_hostname: str | typing.Literal[False] | None = None, + assert_fingerprint: str | None = None, + ca_cert_dir: str | None = None, + **conn_kw: typing.Any, + ) -> None: + super().__init__( + host, + port, + timeout, + maxsize, + block, + headers, + retries, + _proxy, + _proxy_headers, + **conn_kw, + ) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.ca_certs = ca_certs + self.ca_cert_dir = ca_cert_dir + self.ssl_version = ssl_version + self.ssl_minimum_version = ssl_minimum_version + self.ssl_maximum_version = ssl_maximum_version + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + + def _prepare_proxy(self, conn: HTTPSConnection) -> None: # type: ignore[override] + """Establishes a tunnel connection through HTTP CONNECT.""" + if self.proxy and self.proxy.scheme == "https": + tunnel_scheme = "https" + else: + tunnel_scheme = "http" + + conn.set_tunnel( + scheme=tunnel_scheme, + host=self._tunnel_host, + port=self.port, + headers=self.proxy_headers, + ) + conn.connect() + + def _new_conn(self) -> BaseHTTPSConnection: + """ + Return a fresh :class:`urllib3.connection.HTTPConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTPS connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "443", + ) + + if not self.ConnectionCls or self.ConnectionCls is DummyConnection: # type: ignore[comparison-overlap] + raise ImportError( + "Can't connect to HTTPS URL because the SSL module is not available." + ) + + actual_host: str = self.host + actual_port = self.port + if self.proxy is not None and self.proxy.host is not None: + actual_host = self.proxy.host + actual_port = self.proxy.port + + return self.ConnectionCls( + host=actual_host, + port=actual_port, + timeout=self.timeout.connect_timeout, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ssl_version=self.ssl_version, + ssl_minimum_version=self.ssl_minimum_version, + ssl_maximum_version=self.ssl_maximum_version, + **self.conn_kw, + ) + + def _validate_conn(self, conn: BaseHTTPConnection) -> None: + """ + Called right before a request is made, after the socket is created. + """ + super()._validate_conn(conn) + + # Force connect early to allow us to validate the connection. + if conn.is_closed: + conn.connect() + + # TODO revise this, see https://github.com/urllib3/urllib3/issues/2791 + if not conn.is_verified and not conn.proxy_is_verified: + warnings.warn( + ( + f"Unverified HTTPS request is being made to host '{conn.host}'. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" + "#tls-warnings" + ), + InsecureRequestWarning, + ) + + +def connection_from_url(url: str, **kw: typing.Any) -> HTTPConnectionPool: + """ + Given a url, return an :class:`.ConnectionPool` instance of its host. + + This is a shortcut for not having to parse out the scheme, host, and port + of the url before creating an :class:`.ConnectionPool` instance. + + :param url: + Absolute URL string that must include the scheme. Port is optional. + + :param \\**kw: + Passes additional parameters to the constructor of the appropriate + :class:`.ConnectionPool`. Useful for specifying things like + timeout, maxsize, headers, etc. + + Example:: + + >>> conn = connection_from_url('http://google.com/') + >>> r = conn.request('GET', '/') + """ + scheme, _, host, port, *_ = parse_url(url) + scheme = scheme or "http" + port = port or port_by_scheme.get(scheme, 80) + if scheme == "https": + return HTTPSConnectionPool(host, port=port, **kw) # type: ignore[arg-type] + else: + return HTTPConnectionPool(host, port=port, **kw) # type: ignore[arg-type] + + +@typing.overload +def _normalize_host(host: None, scheme: str | None) -> None: ... + + +@typing.overload +def _normalize_host(host: str, scheme: str | None) -> str: ... + + +def _normalize_host(host: str | None, scheme: str | None) -> str | None: + """ + Normalize hosts for comparisons and use with sockets. + """ + + host = normalize_host(host, scheme) + + # httplib doesn't like it when we include brackets in IPv6 addresses + # Specifically, if we include brackets but also pass the port then + # httplib crazily doubles up the square brackets on the Host header. + # Instead, we need to make sure we never pass ``None`` as the port. + # However, for backward compatibility reasons we can't actually + # *assert* that. See http://bugs.python.org/issue28539 + if host and host.startswith("[") and host.endswith("]"): + host = host[1:-1] + return host + + +def _url_from_pool( + pool: HTTPConnectionPool | HTTPSConnectionPool, path: str | None = None +) -> str: + """Returns the URL from a given connection pool. This is mainly used for testing and logging.""" + return Url(scheme=pool.scheme, host=pool.host, port=pool.port, path=path).url + + +def _close_pool_connections(pool: queue.LifoQueue[typing.Any]) -> None: + """Drains a queue of connections and closes each one.""" + try: + while True: + conn = pool.get(block=False) + if conn: + conn.close() + except queue.Empty: + pass # Done. diff --git a/aws/lambda_demo/urllib3/contrib/__init__.py b/aws/lambda_demo/urllib3/contrib/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/aws/lambda_demo/urllib3/contrib/emscripten/__init__.py b/aws/lambda_demo/urllib3/contrib/emscripten/__init__.py new file mode 100644 index 000000000..8a3c5bebd --- /dev/null +++ b/aws/lambda_demo/urllib3/contrib/emscripten/__init__.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +import urllib3.connection + +from ...connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from .connection import EmscriptenHTTPConnection, EmscriptenHTTPSConnection + + +def inject_into_urllib3() -> None: + # override connection classes to use emscripten specific classes + # n.b. mypy complains about the overriding of classes below + # if it isn't ignored + HTTPConnectionPool.ConnectionCls = EmscriptenHTTPConnection + HTTPSConnectionPool.ConnectionCls = EmscriptenHTTPSConnection + urllib3.connection.HTTPConnection = EmscriptenHTTPConnection # type: ignore[misc,assignment] + urllib3.connection.HTTPSConnection = EmscriptenHTTPSConnection # type: ignore[misc,assignment] diff --git a/aws/lambda_demo/urllib3/contrib/emscripten/connection.py b/aws/lambda_demo/urllib3/contrib/emscripten/connection.py new file mode 100644 index 000000000..41bfd2797 --- /dev/null +++ b/aws/lambda_demo/urllib3/contrib/emscripten/connection.py @@ -0,0 +1,255 @@ +from __future__ import annotations + +import os +import typing + +# use http.client.HTTPException for consistency with non-emscripten +from http.client import HTTPException as HTTPException # noqa: F401 +from http.client import ResponseNotReady + +from ..._base_connection import _TYPE_BODY +from ...connection import HTTPConnection, ProxyConfig, port_by_scheme +from ...exceptions import TimeoutError +from ...response import BaseHTTPResponse +from ...util.connection import _TYPE_SOCKET_OPTIONS +from ...util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT +from ...util.url import Url +from .fetch import _RequestError, _TimeoutError, send_request, send_streaming_request +from .request import EmscriptenRequest +from .response import EmscriptenHttpResponseWrapper, EmscriptenResponse + +if typing.TYPE_CHECKING: + from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection + + +class EmscriptenHTTPConnection: + default_port: typing.ClassVar[int] = port_by_scheme["http"] + default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] + + timeout: None | (float) + + host: str + port: int + blocksize: int + source_address: tuple[str, int] | None + socket_options: _TYPE_SOCKET_OPTIONS | None + + proxy: Url | None + proxy_config: ProxyConfig | None + + is_verified: bool = False + proxy_is_verified: bool | None = None + + _response: EmscriptenResponse | None + + def __init__( + self, + host: str, + port: int = 0, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 8192, + socket_options: _TYPE_SOCKET_OPTIONS | None = None, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + ) -> None: + self.host = host + self.port = port + self.timeout = timeout if isinstance(timeout, float) else 0.0 + self.scheme = "http" + self._closed = True + self._response = None + # ignore these things because we don't + # have control over that stuff + self.proxy = None + self.proxy_config = None + self.blocksize = blocksize + self.source_address = None + self.socket_options = None + self.is_verified = False + + def set_tunnel( + self, + host: str, + port: int | None = 0, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: + pass + + def connect(self) -> None: + pass + + def request( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + # We know *at least* botocore is depending on the order of the + # first 3 parameters so to be safe we only mark the later ones + # as keyword-only to ensure we have space to extend. + *, + chunked: bool = False, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> None: + self._closed = False + if url.startswith("/"): + # no scheme / host / port included, make a full url + url = f"{self.scheme}://{self.host}:{self.port}" + url + request = EmscriptenRequest( + url=url, + method=method, + timeout=self.timeout if self.timeout else 0, + decode_content=decode_content, + ) + request.set_body(body) + if headers: + for k, v in headers.items(): + request.set_header(k, v) + self._response = None + try: + if not preload_content: + self._response = send_streaming_request(request) + if self._response is None: + self._response = send_request(request) + except _TimeoutError as e: + raise TimeoutError(e.message) from e + except _RequestError as e: + raise HTTPException(e.message) from e + + def getresponse(self) -> BaseHTTPResponse: + if self._response is not None: + return EmscriptenHttpResponseWrapper( + internal_response=self._response, + url=self._response.request.url, + connection=self, + ) + else: + raise ResponseNotReady() + + def close(self) -> None: + self._closed = True + self._response = None + + @property + def is_closed(self) -> bool: + """Whether the connection either is brand new or has been previously closed. + If this property is True then both ``is_connected`` and ``has_connected_to_proxy`` + properties must be False. + """ + return self._closed + + @property + def is_connected(self) -> bool: + """Whether the connection is actively connected to any origin (proxy or target)""" + return True + + @property + def has_connected_to_proxy(self) -> bool: + """Whether the connection has successfully connected to its proxy. + This returns False if no proxy is in use. Used to determine whether + errors are coming from the proxy layer or from tunnelling to the target origin. + """ + return False + + +class EmscriptenHTTPSConnection(EmscriptenHTTPConnection): + default_port = port_by_scheme["https"] + # all this is basically ignored, as browser handles https + cert_reqs: int | str | None = None + ca_certs: str | None = None + ca_cert_dir: str | None = None + ca_cert_data: None | str | bytes = None + cert_file: str | None + key_file: str | None + key_password: str | None + ssl_context: typing.Any | None + ssl_version: int | str | None = None + ssl_minimum_version: int | None = None + ssl_maximum_version: int | None = None + assert_hostname: None | str | typing.Literal[False] + assert_fingerprint: str | None = None + + def __init__( + self, + host: str, + port: int = 0, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: ( + None | _TYPE_SOCKET_OPTIONS + ) = HTTPConnection.default_socket_options, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + cert_reqs: int | str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + server_hostname: str | None = None, + ssl_context: typing.Any | None = None, + ca_certs: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + ssl_version: int | str | None = None, # Deprecated + cert_file: str | None = None, + key_file: str | None = None, + key_password: str | None = None, + ) -> None: + super().__init__( + host, + port=port, + timeout=timeout, + source_address=source_address, + blocksize=blocksize, + socket_options=socket_options, + proxy=proxy, + proxy_config=proxy_config, + ) + self.scheme = "https" + + self.key_file = key_file + self.cert_file = cert_file + self.key_password = key_password + self.ssl_context = ssl_context + self.server_hostname = server_hostname + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ssl_version = ssl_version + self.ssl_minimum_version = ssl_minimum_version + self.ssl_maximum_version = ssl_maximum_version + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data + + self.cert_reqs = None + + # The browser will automatically verify all requests. + # We have no control over that setting. + self.is_verified = True + + def set_cert( + self, + key_file: str | None = None, + cert_file: str | None = None, + cert_reqs: int | str | None = None, + key_password: str | None = None, + ca_certs: str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ) -> None: + pass + + +# verify that this class implements BaseHTTP(s) connection correctly +if typing.TYPE_CHECKING: + _supports_http_protocol: BaseHTTPConnection = EmscriptenHTTPConnection("", 0) + _supports_https_protocol: BaseHTTPSConnection = EmscriptenHTTPSConnection("", 0) diff --git a/aws/lambda_demo/urllib3/contrib/emscripten/emscripten_fetch_worker.js b/aws/lambda_demo/urllib3/contrib/emscripten/emscripten_fetch_worker.js new file mode 100644 index 000000000..243b86222 --- /dev/null +++ b/aws/lambda_demo/urllib3/contrib/emscripten/emscripten_fetch_worker.js @@ -0,0 +1,110 @@ +let Status = { + SUCCESS_HEADER: -1, + SUCCESS_EOF: -2, + ERROR_TIMEOUT: -3, + ERROR_EXCEPTION: -4, +}; + +let connections = {}; +let nextConnectionID = 1; +const encoder = new TextEncoder(); + +self.addEventListener("message", async function (event) { + if (event.data.close) { + let connectionID = event.data.close; + delete connections[connectionID]; + return; + } else if (event.data.getMore) { + let connectionID = event.data.getMore; + let { curOffset, value, reader, intBuffer, byteBuffer } = + connections[connectionID]; + // if we still have some in buffer, then just send it back straight away + if (!value || curOffset >= value.length) { + // read another buffer if required + try { + let readResponse = await reader.read(); + + if (readResponse.done) { + // read everything - clear connection and return + delete connections[connectionID]; + Atomics.store(intBuffer, 0, Status.SUCCESS_EOF); + Atomics.notify(intBuffer, 0); + // finished reading successfully + // return from event handler + return; + } + curOffset = 0; + connections[connectionID].value = readResponse.value; + value = readResponse.value; + } catch (error) { + console.log("Request exception:", error); + let errorBytes = encoder.encode(error.message); + let written = errorBytes.length; + byteBuffer.set(errorBytes); + intBuffer[1] = written; + Atomics.store(intBuffer, 0, Status.ERROR_EXCEPTION); + Atomics.notify(intBuffer, 0); + } + } + + // send as much buffer as we can + let curLen = value.length - curOffset; + if (curLen > byteBuffer.length) { + curLen = byteBuffer.length; + } + byteBuffer.set(value.subarray(curOffset, curOffset + curLen), 0); + + Atomics.store(intBuffer, 0, curLen); // store current length in bytes + Atomics.notify(intBuffer, 0); + curOffset += curLen; + connections[connectionID].curOffset = curOffset; + + return; + } else { + // start fetch + let connectionID = nextConnectionID; + nextConnectionID += 1; + const intBuffer = new Int32Array(event.data.buffer); + const byteBuffer = new Uint8Array(event.data.buffer, 8); + try { + const response = await fetch(event.data.url, event.data.fetchParams); + // return the headers first via textencoder + var headers = []; + for (const pair of response.headers.entries()) { + headers.push([pair[0], pair[1]]); + } + let headerObj = { + headers: headers, + status: response.status, + connectionID, + }; + const headerText = JSON.stringify(headerObj); + let headerBytes = encoder.encode(headerText); + let written = headerBytes.length; + byteBuffer.set(headerBytes); + intBuffer[1] = written; + // make a connection + connections[connectionID] = { + reader: response.body.getReader(), + intBuffer: intBuffer, + byteBuffer: byteBuffer, + value: undefined, + curOffset: 0, + }; + // set header ready + Atomics.store(intBuffer, 0, Status.SUCCESS_HEADER); + Atomics.notify(intBuffer, 0); + // all fetching after this goes through a new postmessage call with getMore + // this allows for parallel requests + } catch (error) { + console.log("Request exception:", error); + let errorBytes = encoder.encode(error.message); + let written = errorBytes.length; + byteBuffer.set(errorBytes); + intBuffer[1] = written; + Atomics.store(intBuffer, 0, Status.ERROR_EXCEPTION); + Atomics.notify(intBuffer, 0); + } + } +}); +self.postMessage({ inited: true }); diff --git a/aws/lambda_demo/urllib3/contrib/emscripten/fetch.py b/aws/lambda_demo/urllib3/contrib/emscripten/fetch.py new file mode 100644 index 000000000..a514306e1 --- /dev/null +++ b/aws/lambda_demo/urllib3/contrib/emscripten/fetch.py @@ -0,0 +1,708 @@ +""" +Support for streaming http requests in emscripten. + +A few caveats - + +If your browser (or Node.js) has WebAssembly JavaScript Promise Integration enabled +https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md +*and* you launch pyodide using `pyodide.runPythonAsync`, this will fetch data using the +JavaScript asynchronous fetch api (wrapped via `pyodide.ffi.call_sync`). In this case +timeouts and streaming should just work. + +Otherwise, it uses a combination of XMLHttpRequest and a web-worker for streaming. + +This approach has several caveats: + +Firstly, you can't do streaming http in the main UI thread, because atomics.wait isn't allowed. +Streaming only works if you're running pyodide in a web worker. + +Secondly, this uses an extra web worker and SharedArrayBuffer to do the asynchronous fetch +operation, so it requires that you have crossOriginIsolation enabled, by serving over https +(or from localhost) with the two headers below set: + + Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp + +You can tell if cross origin isolation is successfully enabled by looking at the global crossOriginIsolated variable in +JavaScript console. If it isn't, streaming requests will fallback to XMLHttpRequest, i.e. getting the whole +request into a buffer and then returning it. it shows a warning in the JavaScript console in this case. + +Finally, the webworker which does the streaming fetch is created on initial import, but will only be started once +control is returned to javascript. Call `await wait_for_streaming_ready()` to wait for streaming fetch. + +NB: in this code, there are a lot of JavaScript objects. They are named js_* +to make it clear what type of object they are. +""" + +from __future__ import annotations + +import io +import json +from email.parser import Parser +from importlib.resources import files +from typing import TYPE_CHECKING, Any + +import js # type: ignore[import-not-found] +from pyodide.ffi import ( # type: ignore[import-not-found] + JsArray, + JsException, + JsProxy, + to_js, +) + +if TYPE_CHECKING: + from typing_extensions import Buffer + +from .request import EmscriptenRequest +from .response import EmscriptenResponse + +""" +There are some headers that trigger unintended CORS preflight requests. +See also https://github.com/koenvo/pyodide-http/issues/22 +""" +HEADERS_TO_IGNORE = ("user-agent",) + +SUCCESS_HEADER = -1 +SUCCESS_EOF = -2 +ERROR_TIMEOUT = -3 +ERROR_EXCEPTION = -4 + +_STREAMING_WORKER_CODE = ( + files(__package__) + .joinpath("emscripten_fetch_worker.js") + .read_text(encoding="utf-8") +) + + +class _RequestError(Exception): + def __init__( + self, + message: str | None = None, + *, + request: EmscriptenRequest | None = None, + response: EmscriptenResponse | None = None, + ): + self.request = request + self.response = response + self.message = message + super().__init__(self.message) + + +class _StreamingError(_RequestError): + pass + + +class _TimeoutError(_RequestError): + pass + + +def _obj_from_dict(dict_val: dict[str, Any]) -> JsProxy: + return to_js(dict_val, dict_converter=js.Object.fromEntries) + + +class _ReadStream(io.RawIOBase): + def __init__( + self, + int_buffer: JsArray, + byte_buffer: JsArray, + timeout: float, + worker: JsProxy, + connection_id: int, + request: EmscriptenRequest, + ): + self.int_buffer = int_buffer + self.byte_buffer = byte_buffer + self.read_pos = 0 + self.read_len = 0 + self.connection_id = connection_id + self.worker = worker + self.timeout = int(1000 * timeout) if timeout > 0 else None + self.is_live = True + self._is_closed = False + self.request: EmscriptenRequest | None = request + + def __del__(self) -> None: + self.close() + + # this is compatible with _base_connection + def is_closed(self) -> bool: + return self._is_closed + + # for compatibility with RawIOBase + @property + def closed(self) -> bool: + return self.is_closed() + + def close(self) -> None: + if self.is_closed(): + return + self.read_len = 0 + self.read_pos = 0 + self.int_buffer = None + self.byte_buffer = None + self._is_closed = True + self.request = None + if self.is_live: + self.worker.postMessage(_obj_from_dict({"close": self.connection_id})) + self.is_live = False + super().close() + + def readable(self) -> bool: + return True + + def writable(self) -> bool: + return False + + def seekable(self) -> bool: + return False + + def readinto(self, byte_obj: Buffer) -> int: + if not self.int_buffer: + raise _StreamingError( + "No buffer for stream in _ReadStream.readinto", + request=self.request, + response=None, + ) + if self.read_len == 0: + # wait for the worker to send something + js.Atomics.store(self.int_buffer, 0, ERROR_TIMEOUT) + self.worker.postMessage(_obj_from_dict({"getMore": self.connection_id})) + if ( + js.Atomics.wait(self.int_buffer, 0, ERROR_TIMEOUT, self.timeout) + == "timed-out" + ): + raise _TimeoutError + data_len = self.int_buffer[0] + if data_len > 0: + self.read_len = data_len + self.read_pos = 0 + elif data_len == ERROR_EXCEPTION: + string_len = self.int_buffer[1] + # decode the error string + js_decoder = js.TextDecoder.new() + json_str = js_decoder.decode(self.byte_buffer.slice(0, string_len)) + raise _StreamingError( + f"Exception thrown in fetch: {json_str}", + request=self.request, + response=None, + ) + else: + # EOF, free the buffers and return zero + # and free the request + self.is_live = False + self.close() + return 0 + # copy from int32array to python bytes + ret_length = min(self.read_len, len(memoryview(byte_obj))) + subarray = self.byte_buffer.subarray( + self.read_pos, self.read_pos + ret_length + ).to_py() + memoryview(byte_obj)[0:ret_length] = subarray + self.read_len -= ret_length + self.read_pos += ret_length + return ret_length + + +class _StreamingFetcher: + def __init__(self) -> None: + # make web-worker and data buffer on startup + self.streaming_ready = False + + js_data_blob = js.Blob.new( + to_js([_STREAMING_WORKER_CODE], create_pyproxies=False), + _obj_from_dict({"type": "application/javascript"}), + ) + + def promise_resolver(js_resolve_fn: JsProxy, js_reject_fn: JsProxy) -> None: + def onMsg(e: JsProxy) -> None: + self.streaming_ready = True + js_resolve_fn(e) + + def onErr(e: JsProxy) -> None: + js_reject_fn(e) # Defensive: never happens in ci + + self.js_worker.onmessage = onMsg + self.js_worker.onerror = onErr + + js_data_url = js.URL.createObjectURL(js_data_blob) + self.js_worker = js.globalThis.Worker.new(js_data_url) + self.js_worker_ready_promise = js.globalThis.Promise.new(promise_resolver) + + def send(self, request: EmscriptenRequest) -> EmscriptenResponse: + headers = { + k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE + } + + body = request.body + fetch_data = {"headers": headers, "body": to_js(body), "method": request.method} + # start the request off in the worker + timeout = int(1000 * request.timeout) if request.timeout > 0 else None + js_shared_buffer = js.SharedArrayBuffer.new(1048576) + js_int_buffer = js.Int32Array.new(js_shared_buffer) + js_byte_buffer = js.Uint8Array.new(js_shared_buffer, 8) + + js.Atomics.store(js_int_buffer, 0, ERROR_TIMEOUT) + js.Atomics.notify(js_int_buffer, 0) + js_absolute_url = js.URL.new(request.url, js.location).href + self.js_worker.postMessage( + _obj_from_dict( + { + "buffer": js_shared_buffer, + "url": js_absolute_url, + "fetchParams": fetch_data, + } + ) + ) + # wait for the worker to send something + js.Atomics.wait(js_int_buffer, 0, ERROR_TIMEOUT, timeout) + if js_int_buffer[0] == ERROR_TIMEOUT: + raise _TimeoutError( + "Timeout connecting to streaming request", + request=request, + response=None, + ) + elif js_int_buffer[0] == SUCCESS_HEADER: + # got response + # header length is in second int of intBuffer + string_len = js_int_buffer[1] + # decode the rest to a JSON string + js_decoder = js.TextDecoder.new() + # this does a copy (the slice) because decode can't work on shared array + # for some silly reason + json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len)) + # get it as an object + response_obj = json.loads(json_str) + return EmscriptenResponse( + request=request, + status_code=response_obj["status"], + headers=response_obj["headers"], + body=_ReadStream( + js_int_buffer, + js_byte_buffer, + request.timeout, + self.js_worker, + response_obj["connectionID"], + request, + ), + ) + elif js_int_buffer[0] == ERROR_EXCEPTION: + string_len = js_int_buffer[1] + # decode the error string + js_decoder = js.TextDecoder.new() + json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len)) + raise _StreamingError( + f"Exception thrown in fetch: {json_str}", request=request, response=None + ) + else: + raise _StreamingError( + f"Unknown status from worker in fetch: {js_int_buffer[0]}", + request=request, + response=None, + ) + + +class _JSPIReadStream(io.RawIOBase): + """ + A read stream that uses pyodide.ffi.run_sync to read from a JavaScript fetch + response. This requires support for WebAssembly JavaScript Promise Integration + in the containing browser, and for pyodide to be launched via runPythonAsync. + + :param js_read_stream: + The JavaScript stream reader + + :param timeout: + Timeout in seconds + + :param request: + The request we're handling + + :param response: + The response this stream relates to + + :param js_abort_controller: + A JavaScript AbortController object, used for timeouts + """ + + def __init__( + self, + js_read_stream: Any, + timeout: float, + request: EmscriptenRequest, + response: EmscriptenResponse, + js_abort_controller: Any, # JavaScript AbortController for timeouts + ): + self.js_read_stream = js_read_stream + self.timeout = timeout + self._is_closed = False + self._is_done = False + self.request: EmscriptenRequest | None = request + self.response: EmscriptenResponse | None = response + self.current_buffer = None + self.current_buffer_pos = 0 + self.js_abort_controller = js_abort_controller + + def __del__(self) -> None: + self.close() + + # this is compatible with _base_connection + def is_closed(self) -> bool: + return self._is_closed + + # for compatibility with RawIOBase + @property + def closed(self) -> bool: + return self.is_closed() + + def close(self) -> None: + if self.is_closed(): + return + self.read_len = 0 + self.read_pos = 0 + self.js_read_stream.cancel() + self.js_read_stream = None + self._is_closed = True + self._is_done = True + self.request = None + self.response = None + super().close() + + def readable(self) -> bool: + return True + + def writable(self) -> bool: + return False + + def seekable(self) -> bool: + return False + + def _get_next_buffer(self) -> bool: + result_js = _run_sync_with_timeout( + self.js_read_stream.read(), + self.timeout, + self.js_abort_controller, + request=self.request, + response=self.response, + ) + if result_js.done: + self._is_done = True + return False + else: + self.current_buffer = result_js.value.to_py() + self.current_buffer_pos = 0 + return True + + def readinto(self, byte_obj: Buffer) -> int: + if self.current_buffer is None: + if not self._get_next_buffer() or self.current_buffer is None: + self.close() + return 0 + ret_length = min( + len(byte_obj), len(self.current_buffer) - self.current_buffer_pos + ) + byte_obj[0:ret_length] = self.current_buffer[ + self.current_buffer_pos : self.current_buffer_pos + ret_length + ] + self.current_buffer_pos += ret_length + if self.current_buffer_pos == len(self.current_buffer): + self.current_buffer = None + return ret_length + + +# check if we are in a worker or not +def is_in_browser_main_thread() -> bool: + return hasattr(js, "window") and hasattr(js, "self") and js.self == js.window + + +def is_cross_origin_isolated() -> bool: + return hasattr(js, "crossOriginIsolated") and js.crossOriginIsolated + + +def is_in_node() -> bool: + return ( + hasattr(js, "process") + and hasattr(js.process, "release") + and hasattr(js.process.release, "name") + and js.process.release.name == "node" + ) + + +def is_worker_available() -> bool: + return hasattr(js, "Worker") and hasattr(js, "Blob") + + +_fetcher: _StreamingFetcher | None = None + +if is_worker_available() and ( + (is_cross_origin_isolated() and not is_in_browser_main_thread()) + and (not is_in_node()) +): + _fetcher = _StreamingFetcher() +else: + _fetcher = None + + +NODE_JSPI_ERROR = ( + "urllib3 only works in Node.js with pyodide.runPythonAsync" + " and requires the flag --experimental-wasm-stack-switching in " + " versions of node <24." +) + + +def send_streaming_request(request: EmscriptenRequest) -> EmscriptenResponse | None: + if has_jspi(): + return send_jspi_request(request, True) + elif is_in_node(): + raise _RequestError( + message=NODE_JSPI_ERROR, + request=request, + response=None, + ) + + if _fetcher and streaming_ready(): + return _fetcher.send(request) + else: + _show_streaming_warning() + return None + + +_SHOWN_TIMEOUT_WARNING = False + + +def _show_timeout_warning() -> None: + global _SHOWN_TIMEOUT_WARNING + if not _SHOWN_TIMEOUT_WARNING: + _SHOWN_TIMEOUT_WARNING = True + message = "Warning: Timeout is not available on main browser thread" + js.console.warn(message) + + +_SHOWN_STREAMING_WARNING = False + + +def _show_streaming_warning() -> None: + global _SHOWN_STREAMING_WARNING + if not _SHOWN_STREAMING_WARNING: + _SHOWN_STREAMING_WARNING = True + message = "Can't stream HTTP requests because: \n" + if not is_cross_origin_isolated(): + message += " Page is not cross-origin isolated\n" + if is_in_browser_main_thread(): + message += " Python is running in main browser thread\n" + if not is_worker_available(): + message += " Worker or Blob classes are not available in this environment." # Defensive: this is always False in browsers that we test in + if streaming_ready() is False: + message += """ Streaming fetch worker isn't ready. If you want to be sure that streaming fetch +is working, you need to call: 'await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()`""" + from js import console + + console.warn(message) + + +def send_request(request: EmscriptenRequest) -> EmscriptenResponse: + if has_jspi(): + return send_jspi_request(request, False) + elif is_in_node(): + raise _RequestError( + message=NODE_JSPI_ERROR, + request=request, + response=None, + ) + try: + js_xhr = js.XMLHttpRequest.new() + + if not is_in_browser_main_thread(): + js_xhr.responseType = "arraybuffer" + if request.timeout: + js_xhr.timeout = int(request.timeout * 1000) + else: + js_xhr.overrideMimeType("text/plain; charset=ISO-8859-15") + if request.timeout: + # timeout isn't available on the main thread - show a warning in console + # if it is set + _show_timeout_warning() + + js_xhr.open(request.method, request.url, False) + for name, value in request.headers.items(): + if name.lower() not in HEADERS_TO_IGNORE: + js_xhr.setRequestHeader(name, value) + + js_xhr.send(to_js(request.body)) + + headers = dict(Parser().parsestr(js_xhr.getAllResponseHeaders())) + + if not is_in_browser_main_thread(): + body = js_xhr.response.to_py().tobytes() + else: + body = js_xhr.response.encode("ISO-8859-15") + return EmscriptenResponse( + status_code=js_xhr.status, headers=headers, body=body, request=request + ) + except JsException as err: + if err.name == "TimeoutError": + raise _TimeoutError(err.message, request=request) + elif err.name == "NetworkError": + raise _RequestError(err.message, request=request) + else: + # general http error + raise _RequestError(err.message, request=request) + + +def send_jspi_request( + request: EmscriptenRequest, streaming: bool +) -> EmscriptenResponse: + """ + Send a request using WebAssembly JavaScript Promise Integration + to wrap the asynchronous JavaScript fetch api (experimental). + + :param request: + Request to send + + :param streaming: + Whether to stream the response + + :return: The response object + :rtype: EmscriptenResponse + """ + timeout = request.timeout + js_abort_controller = js.AbortController.new() + headers = {k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE} + req_body = request.body + fetch_data = { + "headers": headers, + "body": to_js(req_body), + "method": request.method, + "signal": js_abort_controller.signal, + } + # Call JavaScript fetch (async api, returns a promise) + fetcher_promise_js = js.fetch(request.url, _obj_from_dict(fetch_data)) + # Now suspend WebAssembly until we resolve that promise + # or time out. + response_js = _run_sync_with_timeout( + fetcher_promise_js, + timeout, + js_abort_controller, + request=request, + response=None, + ) + headers = {} + header_iter = response_js.headers.entries() + while True: + iter_value_js = header_iter.next() + if getattr(iter_value_js, "done", False): + break + else: + headers[str(iter_value_js.value[0])] = str(iter_value_js.value[1]) + status_code = response_js.status + body: bytes | io.RawIOBase = b"" + + response = EmscriptenResponse( + status_code=status_code, headers=headers, body=b"", request=request + ) + if streaming: + # get via inputstream + if response_js.body is not None: + # get a reader from the fetch response + body_stream_js = response_js.body.getReader() + body = _JSPIReadStream( + body_stream_js, timeout, request, response, js_abort_controller + ) + else: + # get directly via arraybuffer + # n.b. this is another async JavaScript call. + body = _run_sync_with_timeout( + response_js.arrayBuffer(), + timeout, + js_abort_controller, + request=request, + response=response, + ).to_py() + response.body = body + return response + + +def _run_sync_with_timeout( + promise: Any, + timeout: float, + js_abort_controller: Any, + request: EmscriptenRequest | None, + response: EmscriptenResponse | None, +) -> Any: + """ + Await a JavaScript promise synchronously with a timeout which is implemented + via the AbortController + + :param promise: + Javascript promise to await + + :param timeout: + Timeout in seconds + + :param js_abort_controller: + A JavaScript AbortController object, used on timeout + + :param request: + The request being handled + + :param response: + The response being handled (if it exists yet) + + :raises _TimeoutError: If the request times out + :raises _RequestError: If the request raises a JavaScript exception + + :return: The result of awaiting the promise. + """ + timer_id = None + if timeout > 0: + timer_id = js.setTimeout( + js_abort_controller.abort.bind(js_abort_controller), int(timeout * 1000) + ) + try: + from pyodide.ffi import run_sync + + # run_sync here uses WebAssembly JavaScript Promise Integration to + # suspend python until the JavaScript promise resolves. + return run_sync(promise) + except JsException as err: + if err.name == "AbortError": + raise _TimeoutError( + message="Request timed out", request=request, response=response + ) + else: + raise _RequestError(message=err.message, request=request, response=response) + finally: + if timer_id is not None: + js.clearTimeout(timer_id) + + +def has_jspi() -> bool: + """ + Return true if jspi can be used. + + This requires both browser support and also WebAssembly + to be in the correct state - i.e. that the javascript + call into python was async not sync. + + :return: True if jspi can be used. + :rtype: bool + """ + try: + from pyodide.ffi import can_run_sync, run_sync # noqa: F401 + + return bool(can_run_sync()) + except ImportError: + return False + + +def streaming_ready() -> bool | None: + if _fetcher: + return _fetcher.streaming_ready + else: + return None # no fetcher, return None to signify that + + +async def wait_for_streaming_ready() -> bool: + if _fetcher: + await _fetcher.js_worker_ready_promise + return True + else: + return False diff --git a/aws/lambda_demo/urllib3/contrib/emscripten/request.py b/aws/lambda_demo/urllib3/contrib/emscripten/request.py new file mode 100644 index 000000000..e692e692b --- /dev/null +++ b/aws/lambda_demo/urllib3/contrib/emscripten/request.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + +from ..._base_connection import _TYPE_BODY + + +@dataclass +class EmscriptenRequest: + method: str + url: str + params: dict[str, str] | None = None + body: _TYPE_BODY | None = None + headers: dict[str, str] = field(default_factory=dict) + timeout: float = 0 + decode_content: bool = True + + def set_header(self, name: str, value: str) -> None: + self.headers[name.capitalize()] = value + + def set_body(self, body: _TYPE_BODY | None) -> None: + self.body = body diff --git a/aws/lambda_demo/urllib3/contrib/emscripten/response.py b/aws/lambda_demo/urllib3/contrib/emscripten/response.py new file mode 100644 index 000000000..b32b40237 --- /dev/null +++ b/aws/lambda_demo/urllib3/contrib/emscripten/response.py @@ -0,0 +1,285 @@ +from __future__ import annotations + +import json as _json +import logging +import typing +from contextlib import contextmanager +from dataclasses import dataclass +from http.client import HTTPException as HTTPException +from io import BytesIO, IOBase + +from ...exceptions import InvalidHeader, TimeoutError +from ...response import BaseHTTPResponse +from ...util.retry import Retry +from .request import EmscriptenRequest + +if typing.TYPE_CHECKING: + from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection + +log = logging.getLogger(__name__) + + +@dataclass +class EmscriptenResponse: + status_code: int + headers: dict[str, str] + body: IOBase | bytes + request: EmscriptenRequest + + +class EmscriptenHttpResponseWrapper(BaseHTTPResponse): + def __init__( + self, + internal_response: EmscriptenResponse, + url: str | None = None, + connection: BaseHTTPConnection | BaseHTTPSConnection | None = None, + ): + self._pool = None # set by pool class + self._body = None + self._response = internal_response + self._url = url + self._connection = connection + self._closed = False + super().__init__( + headers=internal_response.headers, + status=internal_response.status_code, + request_url=url, + version=0, + version_string="HTTP/?", + reason="", + decode_content=True, + ) + self.length_remaining = self._init_length(self._response.request.method) + self.length_is_certain = False + + @property + def url(self) -> str | None: + return self._url + + @url.setter + def url(self, url: str | None) -> None: + self._url = url + + @property + def connection(self) -> BaseHTTPConnection | BaseHTTPSConnection | None: + return self._connection + + @property + def retries(self) -> Retry | None: + return self._retries + + @retries.setter + def retries(self, retries: Retry | None) -> None: + # Override the request_url if retries has a redirect location. + self._retries = retries + + def stream( + self, amt: int | None = 2**16, decode_content: bool | None = None + ) -> typing.Generator[bytes]: + """ + A generator wrapper for the read() method. A call will block until + ``amt`` bytes have been read from the connection or until the + connection is closed. + + :param amt: + How much of the content to read. The generator will return up to + much data per iteration, but may return less. This is particularly + likely when using compressed data. However, the empty string will + never be returned. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + while True: + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + else: + break + + def _init_length(self, request_method: str | None) -> int | None: + length: int | None + content_length: str | None = self.headers.get("content-length") + + if content_length is not None: + try: + # RFC 7230 section 3.3.2 specifies multiple content lengths can + # be sent in a single Content-Length header + # (e.g. Content-Length: 42, 42). This line ensures the values + # are all valid ints and that as long as the `set` length is 1, + # all values are the same. Otherwise, the header is invalid. + lengths = {int(val) for val in content_length.split(",")} + if len(lengths) > 1: + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % content_length + ) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + else: # if content_length is None + length = None + + # Check for responses that shouldn't include a body + if ( + self.status in (204, 304) + or 100 <= self.status < 200 + or request_method == "HEAD" + ): + length = 0 + + return length + + def read( + self, + amt: int | None = None, + decode_content: bool | None = None, # ignored because browser decodes always + cache_content: bool = False, + ) -> bytes: + if ( + self._closed + or self._response is None + or (isinstance(self._response.body, IOBase) and self._response.body.closed) + ): + return b"" + + with self._error_catcher(): + # body has been preloaded as a string by XmlHttpRequest + if not isinstance(self._response.body, IOBase): + self.length_remaining = len(self._response.body) + self.length_is_certain = True + # wrap body in IOStream + self._response.body = BytesIO(self._response.body) + if amt is not None and amt >= 0: + # don't cache partial content + cache_content = False + data = self._response.body.read(amt) + if self.length_remaining is not None: + self.length_remaining = max(self.length_remaining - len(data), 0) + if (self.length_is_certain and self.length_remaining == 0) or len( + data + ) < amt: + # definitely finished reading, close response stream + self._response.body.close() + return typing.cast(bytes, data) + else: # read all we can (and cache it) + data = self._response.body.read() + if cache_content: + self._body = data + if self.length_remaining is not None: + self.length_remaining = max(self.length_remaining - len(data), 0) + if len(data) == 0 or ( + self.length_is_certain and self.length_remaining == 0 + ): + # definitely finished reading, close response stream + self._response.body.close() + return typing.cast(bytes, data) + + def read_chunked( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> typing.Generator[bytes]: + # chunked is handled by browser + while True: + bytes = self.read(amt, decode_content) + if not bytes: + break + yield bytes + + def release_conn(self) -> None: + if not self._pool or not self._connection: + return None + + self._pool._put_conn(self._connection) + self._connection = None + + def drain_conn(self) -> None: + self.close() + + @property + def data(self) -> bytes: + if self._body: + return self._body + else: + return self.read(cache_content=True) + + def json(self) -> typing.Any: + """ + Deserializes the body of the HTTP response as a Python object. + + The body of the HTTP response must be encoded using UTF-8, as per + `RFC 8529 Section 8.1 `_. + + To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to + your custom decoder instead. + + If the body of the HTTP response is not decodable to UTF-8, a + `UnicodeDecodeError` will be raised. If the body of the HTTP response is not a + valid JSON document, a `json.JSONDecodeError` will be raised. + + Read more :ref:`here `. + + :returns: The body of the HTTP response as a Python object. + """ + data = self.data.decode("utf-8") + return _json.loads(data) + + def close(self) -> None: + if not self._closed: + if isinstance(self._response.body, IOBase): + self._response.body.close() + if self._connection: + self._connection.close() + self._connection = None + self._closed = True + + @contextmanager + def _error_catcher(self) -> typing.Generator[None]: + """ + Catch Emscripten specific exceptions thrown by fetch.py, + instead re-raising urllib3 variants, so that low-level exceptions + are not leaked in the high-level api. + + On exit, release the connection back to the pool. + """ + from .fetch import _RequestError, _TimeoutError # avoid circular import + + clean_exit = False + + try: + yield + # If no exception is thrown, we should avoid cleaning up + # unnecessarily. + clean_exit = True + except _TimeoutError as e: + raise TimeoutError(str(e)) + except _RequestError as e: + raise HTTPException(str(e)) + finally: + # If we didn't terminate cleanly, we need to throw away our + # connection. + if not clean_exit: + # The response may not be closed but we're not going to use it + # anymore so close it now + if ( + isinstance(self._response.body, IOBase) + and not self._response.body.closed + ): + self._response.body.close() + # release the connection back to the pool + self.release_conn() + else: + # If we have read everything from the response stream, + # return the connection back to the pool. + if ( + isinstance(self._response.body, IOBase) + and self._response.body.closed + ): + self.release_conn() diff --git a/aws/lambda_demo/urllib3/contrib/pyopenssl.py b/aws/lambda_demo/urllib3/contrib/pyopenssl.py new file mode 100644 index 000000000..ed6543066 --- /dev/null +++ b/aws/lambda_demo/urllib3/contrib/pyopenssl.py @@ -0,0 +1,554 @@ +""" +Module for using pyOpenSSL as a TLS backend. This module was relevant before +the standard library ``ssl`` module supported SNI, but now that we've dropped +support for Python 2.7 all relevant Python versions support SNI so +**this module is no longer recommended**. + +This needs the following packages installed: + +* `pyOpenSSL`_ (tested with 16.0.0) +* `cryptography`_ (minimum 1.3.4, from pyopenssl) +* `idna`_ (minimum 2.0) + +However, pyOpenSSL depends on cryptography, so while we use all three directly here we +end up having relatively few packages required. + +You can install them with the following command: + +.. code-block:: bash + + $ python -m pip install pyopenssl cryptography idna + +To activate certificate checking, call +:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code +before you begin making HTTP requests. This can be done in a ``sitecustomize`` +module, or at any other time before your application begins using ``urllib3``, +like this: + +.. code-block:: python + + try: + import urllib3.contrib.pyopenssl + urllib3.contrib.pyopenssl.inject_into_urllib3() + except ImportError: + pass + +.. _pyopenssl: https://www.pyopenssl.org +.. _cryptography: https://cryptography.io +.. _idna: https://github.com/kjd/idna +""" + +from __future__ import annotations + +import OpenSSL.SSL # type: ignore[import-untyped] +from cryptography import x509 + +try: + from cryptography.x509 import UnsupportedExtension # type: ignore[attr-defined] +except ImportError: + # UnsupportedExtension is gone in cryptography >= 2.1.0 + class UnsupportedExtension(Exception): # type: ignore[no-redef] + pass + + +import logging +import ssl +import typing +from io import BytesIO +from socket import socket as socket_cls +from socket import timeout + +from .. import util + +if typing.TYPE_CHECKING: + from OpenSSL.crypto import X509 # type: ignore[import-untyped] + + +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] + +# Map from urllib3 to PyOpenSSL compatible parameter-values. +_openssl_versions: dict[int, int] = { + util.ssl_.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] + util.ssl_.PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] + ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, +} + +if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD + +if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD + + +_stdlib_to_openssl_verify = { + ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, + ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, + ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER + + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, +} +_openssl_to_stdlib_verify = {v: k for k, v in _stdlib_to_openssl_verify.items()} + +# The SSLvX values are the most likely to be missing in the future +# but we check them all just to be sure. +_OP_NO_SSLv2_OR_SSLv3: int = getattr(OpenSSL.SSL, "OP_NO_SSLv2", 0) | getattr( + OpenSSL.SSL, "OP_NO_SSLv3", 0 +) +_OP_NO_TLSv1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1", 0) +_OP_NO_TLSv1_1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_1", 0) +_OP_NO_TLSv1_2: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_2", 0) +_OP_NO_TLSv1_3: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_3", 0) + +_openssl_to_ssl_minimum_version: dict[int, int] = { + ssl.TLSVersion.MINIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3, + ssl.TLSVersion.TLSv1: _OP_NO_SSLv2_OR_SSLv3, + ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1, + ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1, + ssl.TLSVersion.TLSv1_3: ( + _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 + ), + ssl.TLSVersion.MAXIMUM_SUPPORTED: ( + _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 + ), +} +_openssl_to_ssl_maximum_version: dict[int, int] = { + ssl.TLSVersion.MINIMUM_SUPPORTED: ( + _OP_NO_SSLv2_OR_SSLv3 + | _OP_NO_TLSv1 + | _OP_NO_TLSv1_1 + | _OP_NO_TLSv1_2 + | _OP_NO_TLSv1_3 + ), + ssl.TLSVersion.TLSv1: ( + _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3 + ), + ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3, + ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_3, + ssl.TLSVersion.TLSv1_3: _OP_NO_SSLv2_OR_SSLv3, + ssl.TLSVersion.MAXIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3, +} + +# OpenSSL will only write 16K at a time +SSL_WRITE_BLOCKSIZE = 16384 + +orig_util_SSLContext = util.ssl_.SSLContext + + +log = logging.getLogger(__name__) + + +def inject_into_urllib3() -> None: + "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support." + + _validate_dependencies_met() + + util.SSLContext = PyOpenSSLContext # type: ignore[assignment] + util.ssl_.SSLContext = PyOpenSSLContext # type: ignore[assignment] + util.IS_PYOPENSSL = True + util.ssl_.IS_PYOPENSSL = True + + +def extract_from_urllib3() -> None: + "Undo monkey-patching by :func:`inject_into_urllib3`." + + util.SSLContext = orig_util_SSLContext + util.ssl_.SSLContext = orig_util_SSLContext + util.IS_PYOPENSSL = False + util.ssl_.IS_PYOPENSSL = False + + +def _validate_dependencies_met() -> None: + """ + Verifies that PyOpenSSL's package-level dependencies have been met. + Throws `ImportError` if they are not met. + """ + # Method added in `cryptography==1.1`; not available in older versions + from cryptography.x509.extensions import Extensions + + if getattr(Extensions, "get_extension_for_class", None) is None: + raise ImportError( + "'cryptography' module missing required functionality. " + "Try upgrading to v1.3.4 or newer." + ) + + # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 + # attribute is only present on those versions. + from OpenSSL.crypto import X509 + + x509 = X509() + if getattr(x509, "_x509", None) is None: + raise ImportError( + "'pyOpenSSL' module missing required functionality. " + "Try upgrading to v0.14 or newer." + ) + + +def _dnsname_to_stdlib(name: str) -> str | None: + """ + Converts a dNSName SubjectAlternativeName field to the form used by the + standard library on the given Python version. + + Cryptography produces a dNSName as a unicode string that was idna-decoded + from ASCII bytes. We need to idna-encode that string to get it back, and + then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib + uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8). + + If the name cannot be idna-encoded then we return None signalling that + the name given should be skipped. + """ + + def idna_encode(name: str) -> bytes | None: + """ + Borrowed wholesale from the Python Cryptography Project. It turns out + that we can't just safely call `idna.encode`: it can explode for + wildcard names. This avoids that problem. + """ + import idna + + try: + for prefix in ["*.", "."]: + if name.startswith(prefix): + name = name[len(prefix) :] + return prefix.encode("ascii") + idna.encode(name) + return idna.encode(name) + except idna.core.IDNAError: + return None + + # Don't send IPv6 addresses through the IDNA encoder. + if ":" in name: + return name + + encoded_name = idna_encode(name) + if encoded_name is None: + return None + return encoded_name.decode("utf-8") + + +def get_subj_alt_name(peer_cert: X509) -> list[tuple[str, str]]: + """ + Given an PyOpenSSL certificate, provides all the subject alternative names. + """ + cert = peer_cert.to_cryptography() + + # We want to find the SAN extension. Ask Cryptography to locate it (it's + # faster than looping in Python) + try: + ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value + except x509.ExtensionNotFound: + # No such extension, return the empty list. + return [] + except ( + x509.DuplicateExtension, + UnsupportedExtension, + x509.UnsupportedGeneralNameType, + UnicodeError, + ) as e: + # A problem has been found with the quality of the certificate. Assume + # no SAN field is present. + log.warning( + "A problem was encountered with the certificate that prevented " + "urllib3 from finding the SubjectAlternativeName field. This can " + "affect certificate validation. The error was %s", + e, + ) + return [] + + # We want to return dNSName and iPAddress fields. We need to cast the IPs + # back to strings because the match_hostname function wants them as + # strings. + # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8 + # decoded. This is pretty frustrating, but that's what the standard library + # does with certificates, and so we need to attempt to do the same. + # We also want to skip over names which cannot be idna encoded. + names = [ + ("DNS", name) + for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) + if name is not None + ] + names.extend( + ("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress) + ) + + return names + + +class WrappedSocket: + """API-compatibility wrapper for Python OpenSSL's Connection-class.""" + + def __init__( + self, + connection: OpenSSL.SSL.Connection, + socket: socket_cls, + suppress_ragged_eofs: bool = True, + ) -> None: + self.connection = connection + self.socket = socket + self.suppress_ragged_eofs = suppress_ragged_eofs + self._io_refs = 0 + self._closed = False + + def fileno(self) -> int: + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self) -> None: + if self._io_refs > 0: + self._io_refs -= 1 + if self._closed: + self.close() + + def recv(self, *args: typing.Any, **kwargs: typing.Any) -> bytes: + try: + data = self.connection.recv(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return b"" + else: + raise OSError(e.args[0], str(e)) from e + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return b"" + else: + raise + except OpenSSL.SSL.WantReadError as e: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout("The read operation timed out") from e + else: + return self.recv(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"read error: {e!r}") from e + else: + return data # type: ignore[no-any-return] + + def recv_into(self, *args: typing.Any, **kwargs: typing.Any) -> int: + try: + return self.connection.recv_into(*args, **kwargs) # type: ignore[no-any-return] + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return 0 + else: + raise OSError(e.args[0], str(e)) from e + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return 0 + else: + raise + except OpenSSL.SSL.WantReadError as e: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise timeout("The read operation timed out") from e + else: + return self.recv_into(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"read error: {e!r}") from e + + def settimeout(self, timeout: float) -> None: + return self.socket.settimeout(timeout) + + def _send_until_done(self, data: bytes) -> int: + while True: + try: + return self.connection.send(data) # type: ignore[no-any-return] + except OpenSSL.SSL.WantWriteError as e: + if not util.wait_for_write(self.socket, self.socket.gettimeout()): + raise timeout() from e + continue + except OpenSSL.SSL.SysCallError as e: + raise OSError(e.args[0], str(e)) from e + + def sendall(self, data: bytes) -> None: + total_sent = 0 + while total_sent < len(data): + sent = self._send_until_done( + data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE] + ) + total_sent += sent + + def shutdown(self, how: int) -> None: + try: + self.connection.shutdown() + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"shutdown error: {e!r}") from e + + def close(self) -> None: + self._closed = True + if self._io_refs <= 0: + self._real_close() + + def _real_close(self) -> None: + try: + return self.connection.close() # type: ignore[no-any-return] + except OpenSSL.SSL.Error: + return + + def getpeercert( + self, binary_form: bool = False + ) -> dict[str, list[typing.Any]] | None: + x509 = self.connection.get_peer_certificate() + + if not x509: + return x509 # type: ignore[no-any-return] + + if binary_form: + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) # type: ignore[no-any-return] + + return { + "subject": ((("commonName", x509.get_subject().CN),),), # type: ignore[dict-item] + "subjectAltName": get_subj_alt_name(x509), + } + + def version(self) -> str: + return self.connection.get_protocol_version_name() # type: ignore[no-any-return] + + def selected_alpn_protocol(self) -> str | None: + alpn_proto = self.connection.get_alpn_proto_negotiated() + return alpn_proto.decode() if alpn_proto else None + + +WrappedSocket.makefile = socket_cls.makefile # type: ignore[attr-defined] + + +class PyOpenSSLContext: + """ + I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible + for translating the interface of the standard library ``SSLContext`` object + to calls into PyOpenSSL. + """ + + def __init__(self, protocol: int) -> None: + self.protocol = _openssl_versions[protocol] + self._ctx = OpenSSL.SSL.Context(self.protocol) + self._options = 0 + self.check_hostname = False + self._minimum_version: int = ssl.TLSVersion.MINIMUM_SUPPORTED + self._maximum_version: int = ssl.TLSVersion.MAXIMUM_SUPPORTED + + @property + def options(self) -> int: + return self._options + + @options.setter + def options(self, value: int) -> None: + self._options = value + self._set_ctx_options() + + @property + def verify_mode(self) -> int: + return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] + + @verify_mode.setter + def verify_mode(self, value: ssl.VerifyMode) -> None: + self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback) + + def set_default_verify_paths(self) -> None: + self._ctx.set_default_verify_paths() + + def set_ciphers(self, ciphers: bytes | str) -> None: + if isinstance(ciphers, str): + ciphers = ciphers.encode("utf-8") + self._ctx.set_cipher_list(ciphers) + + def load_verify_locations( + self, + cafile: str | None = None, + capath: str | None = None, + cadata: bytes | None = None, + ) -> None: + if cafile is not None: + cafile = cafile.encode("utf-8") # type: ignore[assignment] + if capath is not None: + capath = capath.encode("utf-8") # type: ignore[assignment] + try: + self._ctx.load_verify_locations(cafile, capath) + if cadata is not None: + self._ctx.load_verify_locations(BytesIO(cadata)) + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"unable to load trusted certificates: {e!r}") from e + + def load_cert_chain( + self, + certfile: str, + keyfile: str | None = None, + password: str | None = None, + ) -> None: + try: + self._ctx.use_certificate_chain_file(certfile) + if password is not None: + if not isinstance(password, bytes): + password = password.encode("utf-8") # type: ignore[assignment] + self._ctx.set_passwd_cb(lambda *_: password) + self._ctx.use_privatekey_file(keyfile or certfile) + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"Unable to load certificate chain: {e!r}") from e + + def set_alpn_protocols(self, protocols: list[bytes | str]) -> None: + protocols = [util.util.to_bytes(p, "ascii") for p in protocols] + return self._ctx.set_alpn_protos(protocols) # type: ignore[no-any-return] + + def wrap_socket( + self, + sock: socket_cls, + server_side: bool = False, + do_handshake_on_connect: bool = True, + suppress_ragged_eofs: bool = True, + server_hostname: bytes | str | None = None, + ) -> WrappedSocket: + cnx = OpenSSL.SSL.Connection(self._ctx, sock) + + # If server_hostname is an IP, don't use it for SNI, per RFC6066 Section 3 + if server_hostname and not util.ssl_.is_ipaddress(server_hostname): + if isinstance(server_hostname, str): + server_hostname = server_hostname.encode("utf-8") + cnx.set_tlsext_host_name(server_hostname) + + cnx.set_connect_state() + + while True: + try: + cnx.do_handshake() + except OpenSSL.SSL.WantReadError as e: + if not util.wait_for_read(sock, sock.gettimeout()): + raise timeout("select timed out") from e + continue + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"bad handshake: {e!r}") from e + break + + return WrappedSocket(cnx, sock) + + def _set_ctx_options(self) -> None: + self._ctx.set_options( + self._options + | _openssl_to_ssl_minimum_version[self._minimum_version] + | _openssl_to_ssl_maximum_version[self._maximum_version] + ) + + @property + def minimum_version(self) -> int: + return self._minimum_version + + @minimum_version.setter + def minimum_version(self, minimum_version: int) -> None: + self._minimum_version = minimum_version + self._set_ctx_options() + + @property + def maximum_version(self) -> int: + return self._maximum_version + + @maximum_version.setter + def maximum_version(self, maximum_version: int) -> None: + self._maximum_version = maximum_version + self._set_ctx_options() + + +def _verify_callback( + cnx: OpenSSL.SSL.Connection, + x509: X509, + err_no: int, + err_depth: int, + return_code: int, +) -> bool: + return err_no == 0 diff --git a/aws/lambda_demo/urllib3/contrib/socks.py b/aws/lambda_demo/urllib3/contrib/socks.py new file mode 100644 index 000000000..c62b5e033 --- /dev/null +++ b/aws/lambda_demo/urllib3/contrib/socks.py @@ -0,0 +1,228 @@ +""" +This module contains provisional support for SOCKS proxies from within +urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and +SOCKS5. To enable its functionality, either install PySocks or install this +module with the ``socks`` extra. + +The SOCKS implementation supports the full range of urllib3 features. It also +supports the following SOCKS features: + +- SOCKS4A (``proxy_url='socks4a://...``) +- SOCKS4 (``proxy_url='socks4://...``) +- SOCKS5 with remote DNS (``proxy_url='socks5h://...``) +- SOCKS5 with local DNS (``proxy_url='socks5://...``) +- Usernames and passwords for the SOCKS proxy + +.. note:: + It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in + your ``proxy_url`` to ensure that DNS resolution is done from the remote + server instead of client-side when connecting to a domain name. + +SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5 +supports IPv4, IPv6, and domain names. + +When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url`` +will be sent as the ``userid`` section of the SOCKS request: + +.. code-block:: python + + proxy_url="socks4a://@proxy-host" + +When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion +of the ``proxy_url`` will be sent as the username/password to authenticate +with the proxy: + +.. code-block:: python + + proxy_url="socks5h://:@proxy-host" + +""" + +from __future__ import annotations + +try: + import socks # type: ignore[import-not-found] +except ImportError: + import warnings + + from ..exceptions import DependencyWarning + + warnings.warn( + ( + "SOCKS support in urllib3 requires the installation of optional " + "dependencies: specifically, PySocks. For more information, see " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html#socks-proxies" + ), + DependencyWarning, + ) + raise + +import typing +from socket import timeout as SocketTimeout + +from ..connection import HTTPConnection, HTTPSConnection +from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from ..exceptions import ConnectTimeoutError, NewConnectionError +from ..poolmanager import PoolManager +from ..util.url import parse_url + +try: + import ssl +except ImportError: + ssl = None # type: ignore[assignment] + + +class _TYPE_SOCKS_OPTIONS(typing.TypedDict): + socks_version: int + proxy_host: str | None + proxy_port: str | None + username: str | None + password: str | None + rdns: bool + + +class SOCKSConnection(HTTPConnection): + """ + A plain-text HTTP connection that connects via a SOCKS proxy. + """ + + def __init__( + self, + _socks_options: _TYPE_SOCKS_OPTIONS, + *args: typing.Any, + **kwargs: typing.Any, + ) -> None: + self._socks_options = _socks_options + super().__init__(*args, **kwargs) + + def _new_conn(self) -> socks.socksocket: + """ + Establish a new connection via the SOCKS proxy. + """ + extra_kw: dict[str, typing.Any] = {} + if self.source_address: + extra_kw["source_address"] = self.source_address + + if self.socket_options: + extra_kw["socket_options"] = self.socket_options + + try: + conn = socks.create_connection( + (self.host, self.port), + proxy_type=self._socks_options["socks_version"], + proxy_addr=self._socks_options["proxy_host"], + proxy_port=self._socks_options["proxy_port"], + proxy_username=self._socks_options["username"], + proxy_password=self._socks_options["password"], + proxy_rdns=self._socks_options["rdns"], + timeout=self.timeout, + **extra_kw, + ) + + except SocketTimeout as e: + raise ConnectTimeoutError( + self, + f"Connection to {self.host} timed out. (connect timeout={self.timeout})", + ) from e + + except socks.ProxyError as e: + # This is fragile as hell, but it seems to be the only way to raise + # useful errors here. + if e.socket_err: + error = e.socket_err + if isinstance(error, SocketTimeout): + raise ConnectTimeoutError( + self, + f"Connection to {self.host} timed out. (connect timeout={self.timeout})", + ) from e + else: + # Adding `from e` messes with coverage somehow, so it's omitted. + # See #2386. + raise NewConnectionError( + self, f"Failed to establish a new connection: {error}" + ) + else: + raise NewConnectionError( + self, f"Failed to establish a new connection: {e}" + ) from e + + except OSError as e: # Defensive: PySocks should catch all these. + raise NewConnectionError( + self, f"Failed to establish a new connection: {e}" + ) from e + + return conn + + +# We don't need to duplicate the Verified/Unverified distinction from +# urllib3/connection.py here because the HTTPSConnection will already have been +# correctly set to either the Verified or Unverified form by that module. This +# means the SOCKSHTTPSConnection will automatically be the correct type. +class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): + pass + + +class SOCKSHTTPConnectionPool(HTTPConnectionPool): + ConnectionCls = SOCKSConnection + + +class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): + ConnectionCls = SOCKSHTTPSConnection + + +class SOCKSProxyManager(PoolManager): + """ + A version of the urllib3 ProxyManager that routes connections via the + defined SOCKS proxy. + """ + + pool_classes_by_scheme = { + "http": SOCKSHTTPConnectionPool, + "https": SOCKSHTTPSConnectionPool, + } + + def __init__( + self, + proxy_url: str, + username: str | None = None, + password: str | None = None, + num_pools: int = 10, + headers: typing.Mapping[str, str] | None = None, + **connection_pool_kw: typing.Any, + ): + parsed = parse_url(proxy_url) + + if username is None and password is None and parsed.auth is not None: + split = parsed.auth.split(":") + if len(split) == 2: + username, password = split + if parsed.scheme == "socks5": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = False + elif parsed.scheme == "socks5h": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = True + elif parsed.scheme == "socks4": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = False + elif parsed.scheme == "socks4a": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = True + else: + raise ValueError(f"Unable to determine SOCKS version from {proxy_url}") + + self.proxy_url = proxy_url + + socks_options = { + "socks_version": socks_version, + "proxy_host": parsed.host, + "proxy_port": parsed.port, + "username": username, + "password": password, + "rdns": rdns, + } + connection_pool_kw["_socks_options"] = socks_options + + super().__init__(num_pools, headers, **connection_pool_kw) + + self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/aws/lambda_demo/urllib3/exceptions.py b/aws/lambda_demo/urllib3/exceptions.py new file mode 100644 index 000000000..039457828 --- /dev/null +++ b/aws/lambda_demo/urllib3/exceptions.py @@ -0,0 +1,327 @@ +from __future__ import annotations + +import socket +import typing +import warnings +from email.errors import MessageDefect +from http.client import IncompleteRead as httplib_IncompleteRead + +if typing.TYPE_CHECKING: + from .connection import HTTPConnection + from .connectionpool import ConnectionPool + from .response import HTTPResponse + from .util.retry import Retry + +# Base Exceptions + + +class HTTPError(Exception): + """Base exception used by this module.""" + + +class HTTPWarning(Warning): + """Base warning used by this module.""" + + +_TYPE_REDUCE_RESULT = tuple[typing.Callable[..., object], tuple[object, ...]] + + +class PoolError(HTTPError): + """Base exception for errors caused within a pool.""" + + def __init__(self, pool: ConnectionPool, message: str) -> None: + self.pool = pool + super().__init__(f"{pool}: {message}") + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, None) + + +class RequestError(PoolError): + """Base exception for PoolErrors that have associated URLs.""" + + def __init__(self, pool: ConnectionPool, url: str, message: str) -> None: + self.url = url + super().__init__(pool, message) + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, self.url, None) + + +class SSLError(HTTPError): + """Raised when SSL certificate fails in an HTTPS connection.""" + + +class ProxyError(HTTPError): + """Raised when the connection to a proxy fails.""" + + # The original error is also available as __cause__. + original_error: Exception + + def __init__(self, message: str, error: Exception) -> None: + super().__init__(message, error) + self.original_error = error + + +class DecodeError(HTTPError): + """Raised when automatic decoding based on Content-Type fails.""" + + +class ProtocolError(HTTPError): + """Raised when something unexpected happens mid-request/response.""" + + +#: Renamed to ProtocolError but aliased for backwards compatibility. +ConnectionError = ProtocolError + + +# Leaf Exceptions + + +class MaxRetryError(RequestError): + """Raised when the maximum number of retries is exceeded. + + :param pool: The connection pool + :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` + :param str url: The requested Url + :param reason: The underlying error + :type reason: :class:`Exception` + + """ + + def __init__( + self, pool: ConnectionPool, url: str, reason: Exception | None = None + ) -> None: + self.reason = reason + + message = f"Max retries exceeded with url: {url} (Caused by {reason!r})" + + super().__init__(pool, url, message) + + +class HostChangedError(RequestError): + """Raised when an existing pool gets a request for a foreign host.""" + + def __init__( + self, pool: ConnectionPool, url: str, retries: Retry | int = 3 + ) -> None: + message = f"Tried to open a foreign host with url: {url}" + super().__init__(pool, url, message) + self.retries = retries + + +class TimeoutStateError(HTTPError): + """Raised when passing an invalid state to a timeout""" + + +class TimeoutError(HTTPError): + """Raised when a socket timeout error occurs. + + Catching this error will catch both :exc:`ReadTimeoutErrors + ` and :exc:`ConnectTimeoutErrors `. + """ + + +class ReadTimeoutError(TimeoutError, RequestError): + """Raised when a socket timeout occurs while receiving data from a server""" + + +# This timeout error does not have a URL attached and needs to inherit from the +# base HTTPError +class ConnectTimeoutError(TimeoutError): + """Raised when a socket timeout occurs while connecting to a server""" + + +class NewConnectionError(ConnectTimeoutError, HTTPError): + """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" + + def __init__(self, conn: HTTPConnection, message: str) -> None: + self.conn = conn + super().__init__(f"{conn}: {message}") + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, None) + + @property + def pool(self) -> HTTPConnection: + warnings.warn( + "The 'pool' property is deprecated and will be removed " + "in urllib3 v2.1.0. Use 'conn' instead.", + DeprecationWarning, + stacklevel=2, + ) + + return self.conn + + +class NameResolutionError(NewConnectionError): + """Raised when host name resolution fails.""" + + def __init__(self, host: str, conn: HTTPConnection, reason: socket.gaierror): + message = f"Failed to resolve '{host}' ({reason})" + super().__init__(conn, message) + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, None, None) + + +class EmptyPoolError(PoolError): + """Raised when a pool runs out of connections and no more are allowed.""" + + +class FullPoolError(PoolError): + """Raised when we try to add a connection to a full pool in blocking mode.""" + + +class ClosedPoolError(PoolError): + """Raised when a request enters a pool after the pool has been closed.""" + + +class LocationValueError(ValueError, HTTPError): + """Raised when there is something wrong with a given URL input.""" + + +class LocationParseError(LocationValueError): + """Raised when get_host or similar fails to parse the URL input.""" + + def __init__(self, location: str) -> None: + message = f"Failed to parse: {location}" + super().__init__(message) + + self.location = location + + +class URLSchemeUnknown(LocationValueError): + """Raised when a URL input has an unsupported scheme.""" + + def __init__(self, scheme: str): + message = f"Not supported URL scheme {scheme}" + super().__init__(message) + + self.scheme = scheme + + +class ResponseError(HTTPError): + """Used as a container for an error reason supplied in a MaxRetryError.""" + + GENERIC_ERROR = "too many error responses" + SPECIFIC_ERROR = "too many {status_code} error responses" + + +class SecurityWarning(HTTPWarning): + """Warned when performing security reducing actions""" + + +class InsecureRequestWarning(SecurityWarning): + """Warned when making an unverified HTTPS request.""" + + +class NotOpenSSLWarning(SecurityWarning): + """Warned when using unsupported SSL library""" + + +class SystemTimeWarning(SecurityWarning): + """Warned when system time is suspected to be wrong""" + + +class InsecurePlatformWarning(SecurityWarning): + """Warned when certain TLS/SSL configuration is not available on a platform.""" + + +class DependencyWarning(HTTPWarning): + """ + Warned when an attempt is made to import a module with missing optional + dependencies. + """ + + +class ResponseNotChunked(ProtocolError, ValueError): + """Response needs to be chunked in order to read it as chunks.""" + + +class BodyNotHttplibCompatible(HTTPError): + """ + Body should be :class:`http.client.HTTPResponse` like + (have an fp attribute which returns raw chunks) for read_chunked(). + """ + + +class IncompleteRead(HTTPError, httplib_IncompleteRead): + """ + Response length doesn't match expected Content-Length + + Subclass of :class:`http.client.IncompleteRead` to allow int value + for ``partial`` to avoid creating large objects on streamed reads. + """ + + partial: int # type: ignore[assignment] + expected: int + + def __init__(self, partial: int, expected: int) -> None: + self.partial = partial + self.expected = expected + + def __repr__(self) -> str: + return "IncompleteRead(%i bytes read, %i more expected)" % ( + self.partial, + self.expected, + ) + + +class InvalidChunkLength(HTTPError, httplib_IncompleteRead): + """Invalid chunk length in a chunked response.""" + + def __init__(self, response: HTTPResponse, length: bytes) -> None: + self.partial: int = response.tell() # type: ignore[assignment] + self.expected: int | None = response.length_remaining + self.response = response + self.length = length + + def __repr__(self) -> str: + return "InvalidChunkLength(got length %r, %i bytes read)" % ( + self.length, + self.partial, + ) + + +class InvalidHeader(HTTPError): + """The header provided was somehow invalid.""" + + +class ProxySchemeUnknown(AssertionError, URLSchemeUnknown): + """ProxyManager does not support the supplied scheme""" + + # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. + + def __init__(self, scheme: str | None) -> None: + # 'localhost' is here because our URL parser parses + # localhost:8080 -> scheme=localhost, remove if we fix this. + if scheme == "localhost": + scheme = None + if scheme is None: + message = "Proxy URL had no scheme, should start with http:// or https://" + else: + message = f"Proxy URL had unsupported scheme {scheme}, should use http:// or https://" + super().__init__(message) + + +class ProxySchemeUnsupported(ValueError): + """Fetching HTTPS resources through HTTPS proxies is unsupported""" + + +class HeaderParsingError(HTTPError): + """Raised by assert_header_parsing, but we convert it to a log.warning statement.""" + + def __init__( + self, defects: list[MessageDefect], unparsed_data: bytes | str | None + ) -> None: + message = f"{defects or 'Unknown'}, unparsed data: {unparsed_data!r}" + super().__init__(message) + + +class UnrewindableBodyError(HTTPError): + """urllib3 encountered an error when trying to rewind a body""" diff --git a/aws/lambda_demo/urllib3/fields.py b/aws/lambda_demo/urllib3/fields.py new file mode 100644 index 000000000..97c4730cf --- /dev/null +++ b/aws/lambda_demo/urllib3/fields.py @@ -0,0 +1,341 @@ +from __future__ import annotations + +import email.utils +import mimetypes +import typing + +_TYPE_FIELD_VALUE = typing.Union[str, bytes] +_TYPE_FIELD_VALUE_TUPLE = typing.Union[ + _TYPE_FIELD_VALUE, + tuple[str, _TYPE_FIELD_VALUE], + tuple[str, _TYPE_FIELD_VALUE, str], +] + + +def guess_content_type( + filename: str | None, default: str = "application/octet-stream" +) -> str: + """ + Guess the "Content-Type" of a file. + + :param filename: + The filename to guess the "Content-Type" of using :mod:`mimetypes`. + :param default: + If no "Content-Type" can be guessed, default to `default`. + """ + if filename: + return mimetypes.guess_type(filename)[0] or default + return default + + +def format_header_param_rfc2231(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + Helper function to format and quote a single header parameter using the + strategy defined in RFC 2231. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows + `RFC 2388 Section 4.4 `_. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as ``bytes`` or `str``. + :returns: + An RFC-2231-formatted unicode string. + + .. deprecated:: 2.0.0 + Will be removed in urllib3 v2.1.0. This is not valid for + ``multipart/form-data`` header parameters. + """ + import warnings + + warnings.warn( + "'format_header_param_rfc2231' is deprecated and will be " + "removed in urllib3 v2.1.0. This is not valid for " + "multipart/form-data header parameters.", + DeprecationWarning, + stacklevel=2, + ) + + if isinstance(value, bytes): + value = value.decode("utf-8") + + if not any(ch in value for ch in '"\\\r\n'): + result = f'{name}="{value}"' + try: + result.encode("ascii") + except (UnicodeEncodeError, UnicodeDecodeError): + pass + else: + return result + + value = email.utils.encode_rfc2231(value, "utf-8") + value = f"{name}*={value}" + + return value + + +def format_multipart_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + Format and quote a single multipart header parameter. + + This follows the `WHATWG HTML Standard`_ as of 2021/06/10, matching + the behavior of current browser and curl versions. Values are + assumed to be UTF-8. The ``\\n``, ``\\r``, and ``"`` characters are + percent encoded. + + .. _WHATWG HTML Standard: + https://html.spec.whatwg.org/multipage/ + form-control-infrastructure.html#multipart-form-data + + :param name: + The name of the parameter, an ASCII-only ``str``. + :param value: + The value of the parameter, a ``str`` or UTF-8 encoded + ``bytes``. + :returns: + A string ``name="value"`` with the escaped value. + + .. versionchanged:: 2.0.0 + Matches the WHATWG HTML Standard as of 2021/06/10. Control + characters are no longer percent encoded. + + .. versionchanged:: 2.0.0 + Renamed from ``format_header_param_html5`` and + ``format_header_param``. The old names will be removed in + urllib3 v2.1.0. + """ + if isinstance(value, bytes): + value = value.decode("utf-8") + + # percent encode \n \r " + value = value.translate({10: "%0A", 13: "%0D", 34: "%22"}) + return f'{name}="{value}"' + + +def format_header_param_html5(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + .. deprecated:: 2.0.0 + Renamed to :func:`format_multipart_header_param`. Will be + removed in urllib3 v2.1.0. + """ + import warnings + + warnings.warn( + "'format_header_param_html5' has been renamed to " + "'format_multipart_header_param'. The old name will be " + "removed in urllib3 v2.1.0.", + DeprecationWarning, + stacklevel=2, + ) + return format_multipart_header_param(name, value) + + +def format_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + .. deprecated:: 2.0.0 + Renamed to :func:`format_multipart_header_param`. Will be + removed in urllib3 v2.1.0. + """ + import warnings + + warnings.warn( + "'format_header_param' has been renamed to " + "'format_multipart_header_param'. The old name will be " + "removed in urllib3 v2.1.0.", + DeprecationWarning, + stacklevel=2, + ) + return format_multipart_header_param(name, value) + + +class RequestField: + """ + A data container for request body parameters. + + :param name: + The name of this request field. Must be unicode. + :param data: + The data/value body. + :param filename: + An optional filename of the request field. Must be unicode. + :param headers: + An optional dict-like object of headers to initially use for the field. + + .. versionchanged:: 2.0.0 + The ``header_formatter`` parameter is deprecated and will + be removed in urllib3 v2.1.0. + """ + + def __init__( + self, + name: str, + data: _TYPE_FIELD_VALUE, + filename: str | None = None, + headers: typing.Mapping[str, str] | None = None, + header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, + ): + self._name = name + self._filename = filename + self.data = data + self.headers: dict[str, str | None] = {} + if headers: + self.headers = dict(headers) + + if header_formatter is not None: + import warnings + + warnings.warn( + "The 'header_formatter' parameter is deprecated and " + "will be removed in urllib3 v2.1.0.", + DeprecationWarning, + stacklevel=2, + ) + self.header_formatter = header_formatter + else: + self.header_formatter = format_multipart_header_param + + @classmethod + def from_tuples( + cls, + fieldname: str, + value: _TYPE_FIELD_VALUE_TUPLE, + header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, + ) -> RequestField: + """ + A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. + + Supports constructing :class:`~urllib3.fields.RequestField` from + parameter of key/value strings AND key/filetuple. A filetuple is a + (filename, data, MIME type) tuple where the MIME type is optional. + For example:: + + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + + Field names and filenames must be unicode. + """ + filename: str | None + content_type: str | None + data: _TYPE_FIELD_VALUE + + if isinstance(value, tuple): + if len(value) == 3: + filename, data, content_type = value + else: + filename, data = value + content_type = guess_content_type(filename) + else: + filename = None + content_type = None + data = value + + request_param = cls( + fieldname, data, filename=filename, header_formatter=header_formatter + ) + request_param.make_multipart(content_type=content_type) + + return request_param + + def _render_part(self, name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + Override this method to change how each multipart header + parameter is formatted. By default, this calls + :func:`format_multipart_header_param`. + + :param name: + The name of the parameter, an ASCII-only ``str``. + :param value: + The value of the parameter, a ``str`` or UTF-8 encoded + ``bytes``. + + :meta public: + """ + return self.header_formatter(name, value) + + def _render_parts( + self, + header_parts: ( + dict[str, _TYPE_FIELD_VALUE | None] + | typing.Sequence[tuple[str, _TYPE_FIELD_VALUE | None]] + ), + ) -> str: + """ + Helper function to format and quote a single header. + + Useful for single headers that are composed of multiple items. E.g., + 'Content-Disposition' fields. + + :param header_parts: + A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format + as `k1="v1"; k2="v2"; ...`. + """ + iterable: typing.Iterable[tuple[str, _TYPE_FIELD_VALUE | None]] + + parts = [] + if isinstance(header_parts, dict): + iterable = header_parts.items() + else: + iterable = header_parts + + for name, value in iterable: + if value is not None: + parts.append(self._render_part(name, value)) + + return "; ".join(parts) + + def render_headers(self) -> str: + """ + Renders the headers for this request field. + """ + lines = [] + + sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"] + for sort_key in sort_keys: + if self.headers.get(sort_key, False): + lines.append(f"{sort_key}: {self.headers[sort_key]}") + + for header_name, header_value in self.headers.items(): + if header_name not in sort_keys: + if header_value: + lines.append(f"{header_name}: {header_value}") + + lines.append("\r\n") + return "\r\n".join(lines) + + def make_multipart( + self, + content_disposition: str | None = None, + content_type: str | None = None, + content_location: str | None = None, + ) -> None: + """ + Makes this request field into a multipart request field. + + This method overrides "Content-Disposition", "Content-Type" and + "Content-Location" headers to the request parameter. + + :param content_disposition: + The 'Content-Disposition' of the request body. Defaults to 'form-data' + :param content_type: + The 'Content-Type' of the request body. + :param content_location: + The 'Content-Location' of the request body. + + """ + content_disposition = (content_disposition or "form-data") + "; ".join( + [ + "", + self._render_parts( + (("name", self._name), ("filename", self._filename)) + ), + ] + ) + + self.headers["Content-Disposition"] = content_disposition + self.headers["Content-Type"] = content_type + self.headers["Content-Location"] = content_location diff --git a/aws/lambda_demo/urllib3/filepost.py b/aws/lambda_demo/urllib3/filepost.py new file mode 100644 index 000000000..14f70b05b --- /dev/null +++ b/aws/lambda_demo/urllib3/filepost.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import binascii +import codecs +import os +import typing +from io import BytesIO + +from .fields import _TYPE_FIELD_VALUE_TUPLE, RequestField + +writer = codecs.lookup("utf-8")[3] + +_TYPE_FIELDS_SEQUENCE = typing.Sequence[ + typing.Union[tuple[str, _TYPE_FIELD_VALUE_TUPLE], RequestField] +] +_TYPE_FIELDS = typing.Union[ + _TYPE_FIELDS_SEQUENCE, + typing.Mapping[str, _TYPE_FIELD_VALUE_TUPLE], +] + + +def choose_boundary() -> str: + """ + Our embarrassingly-simple replacement for mimetools.choose_boundary. + """ + return binascii.hexlify(os.urandom(16)).decode() + + +def iter_field_objects(fields: _TYPE_FIELDS) -> typing.Iterable[RequestField]: + """ + Iterate over fields. + + Supports list of (k, v) tuples and dicts, and lists of + :class:`~urllib3.fields.RequestField`. + + """ + iterable: typing.Iterable[RequestField | tuple[str, _TYPE_FIELD_VALUE_TUPLE]] + + if isinstance(fields, typing.Mapping): + iterable = fields.items() + else: + iterable = fields + + for field in iterable: + if isinstance(field, RequestField): + yield field + else: + yield RequestField.from_tuples(*field) + + +def encode_multipart_formdata( + fields: _TYPE_FIELDS, boundary: str | None = None +) -> tuple[bytes, str]: + """ + Encode a dictionary of ``fields`` using the multipart/form-data MIME format. + + :param fields: + Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). + Values are processed by :func:`urllib3.fields.RequestField.from_tuples`. + + :param boundary: + If not specified, then a random boundary will be generated using + :func:`urllib3.filepost.choose_boundary`. + """ + body = BytesIO() + if boundary is None: + boundary = choose_boundary() + + for field in iter_field_objects(fields): + body.write(f"--{boundary}\r\n".encode("latin-1")) + + writer(body).write(field.render_headers()) + data = field.data + + if isinstance(data, int): + data = str(data) # Backwards compatibility + + if isinstance(data, str): + writer(body).write(data) + else: + body.write(data) + + body.write(b"\r\n") + + body.write(f"--{boundary}--\r\n".encode("latin-1")) + + content_type = f"multipart/form-data; boundary={boundary}" + + return body.getvalue(), content_type diff --git a/aws/lambda_demo/urllib3/http2/__init__.py b/aws/lambda_demo/urllib3/http2/__init__.py new file mode 100644 index 000000000..133e1d8f2 --- /dev/null +++ b/aws/lambda_demo/urllib3/http2/__init__.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from importlib.metadata import version + +__all__ = [ + "inject_into_urllib3", + "extract_from_urllib3", +] + +import typing + +orig_HTTPSConnection: typing.Any = None + + +def inject_into_urllib3() -> None: + # First check if h2 version is valid + h2_version = version("h2") + if not h2_version.startswith("4."): + raise ImportError( + "urllib3 v2 supports h2 version 4.x.x, currently " + f"the 'h2' module is compiled with {h2_version!r}. " + "See: https://github.com/urllib3/urllib3/issues/3290" + ) + + # Import here to avoid circular dependencies. + from .. import connection as urllib3_connection + from .. import util as urllib3_util + from ..connectionpool import HTTPSConnectionPool + from ..util import ssl_ as urllib3_util_ssl + from .connection import HTTP2Connection + + global orig_HTTPSConnection + orig_HTTPSConnection = urllib3_connection.HTTPSConnection + + HTTPSConnectionPool.ConnectionCls = HTTP2Connection + urllib3_connection.HTTPSConnection = HTTP2Connection # type: ignore[misc] + + # TODO: Offer 'http/1.1' as well, but for testing purposes this is handy. + urllib3_util.ALPN_PROTOCOLS = ["h2"] + urllib3_util_ssl.ALPN_PROTOCOLS = ["h2"] + + +def extract_from_urllib3() -> None: + from .. import connection as urllib3_connection + from .. import util as urllib3_util + from ..connectionpool import HTTPSConnectionPool + from ..util import ssl_ as urllib3_util_ssl + + HTTPSConnectionPool.ConnectionCls = orig_HTTPSConnection + urllib3_connection.HTTPSConnection = orig_HTTPSConnection # type: ignore[misc] + + urllib3_util.ALPN_PROTOCOLS = ["http/1.1"] + urllib3_util_ssl.ALPN_PROTOCOLS = ["http/1.1"] diff --git a/aws/lambda_demo/urllib3/http2/connection.py b/aws/lambda_demo/urllib3/http2/connection.py new file mode 100644 index 000000000..f48614528 --- /dev/null +++ b/aws/lambda_demo/urllib3/http2/connection.py @@ -0,0 +1,356 @@ +from __future__ import annotations + +import logging +import re +import threading +import types +import typing + +import h2.config # type: ignore[import-untyped] +import h2.connection # type: ignore[import-untyped] +import h2.events # type: ignore[import-untyped] + +from .._base_connection import _TYPE_BODY +from .._collections import HTTPHeaderDict +from ..connection import HTTPSConnection, _get_default_user_agent +from ..exceptions import ConnectionError +from ..response import BaseHTTPResponse + +orig_HTTPSConnection = HTTPSConnection + +T = typing.TypeVar("T") + +log = logging.getLogger(__name__) + +RE_IS_LEGAL_HEADER_NAME = re.compile(rb"^[!#$%&'*+\-.^_`|~0-9a-z]+$") +RE_IS_ILLEGAL_HEADER_VALUE = re.compile(rb"[\0\x00\x0a\x0d\r\n]|^[ \r\n\t]|[ \r\n\t]$") + + +def _is_legal_header_name(name: bytes) -> bool: + """ + "An implementation that validates fields according to the definitions in Sections + 5.1 and 5.5 of [HTTP] only needs an additional check that field names do not + include uppercase characters." (https://httpwg.org/specs/rfc9113.html#n-field-validity) + + `http.client._is_legal_header_name` does not validate the field name according to the + HTTP 1.1 spec, so we do that here, in addition to checking for uppercase characters. + + This does not allow for the `:` character in the header name, so should not + be used to validate pseudo-headers. + """ + return bool(RE_IS_LEGAL_HEADER_NAME.match(name)) + + +def _is_illegal_header_value(value: bytes) -> bool: + """ + "A field value MUST NOT contain the zero value (ASCII NUL, 0x00), line feed + (ASCII LF, 0x0a), or carriage return (ASCII CR, 0x0d) at any position. A field + value MUST NOT start or end with an ASCII whitespace character (ASCII SP or HTAB, + 0x20 or 0x09)." (https://httpwg.org/specs/rfc9113.html#n-field-validity) + """ + return bool(RE_IS_ILLEGAL_HEADER_VALUE.search(value)) + + +class _LockedObject(typing.Generic[T]): + """ + A wrapper class that hides a specific object behind a lock. + The goal here is to provide a simple way to protect access to an object + that cannot safely be simultaneously accessed from multiple threads. The + intended use of this class is simple: take hold of it with a context + manager, which returns the protected object. + """ + + __slots__ = ( + "lock", + "_obj", + ) + + def __init__(self, obj: T): + self.lock = threading.RLock() + self._obj = obj + + def __enter__(self) -> T: + self.lock.acquire() + return self._obj + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> None: + self.lock.release() + + +class HTTP2Connection(HTTPSConnection): + def __init__( + self, host: str, port: int | None = None, **kwargs: typing.Any + ) -> None: + self._h2_conn = self._new_h2_conn() + self._h2_stream: int | None = None + self._headers: list[tuple[bytes, bytes]] = [] + + if "proxy" in kwargs or "proxy_config" in kwargs: # Defensive: + raise NotImplementedError("Proxies aren't supported with HTTP/2") + + super().__init__(host, port, **kwargs) + + if self._tunnel_host is not None: + raise NotImplementedError("Tunneling isn't supported with HTTP/2") + + def _new_h2_conn(self) -> _LockedObject[h2.connection.H2Connection]: + config = h2.config.H2Configuration(client_side=True) + return _LockedObject(h2.connection.H2Connection(config=config)) + + def connect(self) -> None: + super().connect() + with self._h2_conn as conn: + conn.initiate_connection() + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + + def putrequest( # type: ignore[override] + self, + method: str, + url: str, + **kwargs: typing.Any, + ) -> None: + """putrequest + This deviates from the HTTPConnection method signature since we never need to override + sending accept-encoding headers or the host header. + """ + if "skip_host" in kwargs: + raise NotImplementedError("`skip_host` isn't supported") + if "skip_accept_encoding" in kwargs: + raise NotImplementedError("`skip_accept_encoding` isn't supported") + + self._request_url = url or "/" + self._validate_path(url) # type: ignore[attr-defined] + + if ":" in self.host: + authority = f"[{self.host}]:{self.port or 443}" + else: + authority = f"{self.host}:{self.port or 443}" + + self._headers.append((b":scheme", b"https")) + self._headers.append((b":method", method.encode())) + self._headers.append((b":authority", authority.encode())) + self._headers.append((b":path", url.encode())) + + with self._h2_conn as conn: + self._h2_stream = conn.get_next_available_stream_id() + + def putheader(self, header: str | bytes, *values: str | bytes) -> None: + # TODO SKIPPABLE_HEADERS from urllib3 are ignored. + header = header.encode() if isinstance(header, str) else header + header = header.lower() # A lot of upstream code uses capitalized headers. + if not _is_legal_header_name(header): + raise ValueError(f"Illegal header name {str(header)}") + + for value in values: + value = value.encode() if isinstance(value, str) else value + if _is_illegal_header_value(value): + raise ValueError(f"Illegal header value {str(value)}") + self._headers.append((header, value)) + + def endheaders(self, message_body: typing.Any = None) -> None: # type: ignore[override] + if self._h2_stream is None: + raise ConnectionError("Must call `putrequest` first.") + + with self._h2_conn as conn: + conn.send_headers( + stream_id=self._h2_stream, + headers=self._headers, + end_stream=(message_body is None), + ) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + self._headers = [] # Reset headers for the next request. + + def send(self, data: typing.Any) -> None: + """Send data to the server. + `data` can be: `str`, `bytes`, an iterable, or file-like objects + that support a .read() method. + """ + if self._h2_stream is None: + raise ConnectionError("Must call `putrequest` first.") + + with self._h2_conn as conn: + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + + if hasattr(data, "read"): # file-like objects + while True: + chunk = data.read(self.blocksize) + if not chunk: + break + if isinstance(chunk, str): + chunk = chunk.encode() # pragma: no cover + conn.send_data(self._h2_stream, chunk, end_stream=False) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + conn.end_stream(self._h2_stream) + return + + if isinstance(data, str): # str -> bytes + data = data.encode() + + try: + if isinstance(data, bytes): + conn.send_data(self._h2_stream, data, end_stream=True) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + else: + for chunk in data: + conn.send_data(self._h2_stream, chunk, end_stream=False) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + conn.end_stream(self._h2_stream) + except TypeError: + raise TypeError( + "`data` should be str, bytes, iterable, or file. got %r" + % type(data) + ) + + def set_tunnel( + self, + host: str, + port: int | None = None, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: + raise NotImplementedError( + "HTTP/2 does not support setting up a tunnel through a proxy" + ) + + def getresponse( # type: ignore[override] + self, + ) -> HTTP2Response: + status = None + data = bytearray() + with self._h2_conn as conn: + end_stream = False + while not end_stream: + # TODO: Arbitrary read value. + if received_data := self.sock.recv(65535): + events = conn.receive_data(received_data) + for event in events: + if isinstance(event, h2.events.ResponseReceived): + headers = HTTPHeaderDict() + for header, value in event.headers: + if header == b":status": + status = int(value.decode()) + else: + headers.add( + header.decode("ascii"), value.decode("ascii") + ) + + elif isinstance(event, h2.events.DataReceived): + data += event.data + conn.acknowledge_received_data( + event.flow_controlled_length, event.stream_id + ) + + elif isinstance(event, h2.events.StreamEnded): + end_stream = True + + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + + assert status is not None + return HTTP2Response( + status=status, + headers=headers, + request_url=self._request_url, + data=bytes(data), + ) + + def request( # type: ignore[override] + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + *, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + **kwargs: typing.Any, + ) -> None: + """Send an HTTP/2 request""" + if "chunked" in kwargs: + # TODO this is often present from upstream. + # raise NotImplementedError("`chunked` isn't supported with HTTP/2") + pass + + if self.sock is not None: + self.sock.settimeout(self.timeout) + + self.putrequest(method, url) + + headers = headers or {} + for k, v in headers.items(): + if k.lower() == "transfer-encoding" and v == "chunked": + continue + else: + self.putheader(k, v) + + if b"user-agent" not in dict(self._headers): + self.putheader(b"user-agent", _get_default_user_agent()) + + if body: + self.endheaders(message_body=body) + self.send(body) + else: + self.endheaders() + + def close(self) -> None: + with self._h2_conn as conn: + try: + conn.close_connection() + if data := conn.data_to_send(): + self.sock.sendall(data) + except Exception: + pass + + # Reset all our HTTP/2 connection state. + self._h2_conn = self._new_h2_conn() + self._h2_stream = None + self._headers = [] + + super().close() + + +class HTTP2Response(BaseHTTPResponse): + # TODO: This is a woefully incomplete response object, but works for non-streaming. + def __init__( + self, + status: int, + headers: HTTPHeaderDict, + request_url: str, + data: bytes, + decode_content: bool = False, # TODO: support decoding + ) -> None: + super().__init__( + status=status, + headers=headers, + # Following CPython, we map HTTP versions to major * 10 + minor integers + version=20, + version_string="HTTP/2", + # No reason phrase in HTTP/2 + reason=None, + decode_content=decode_content, + request_url=request_url, + ) + self._data = data + self.length_remaining = 0 + + @property + def data(self) -> bytes: + return self._data + + def get_redirect_location(self) -> None: + return None + + def close(self) -> None: + pass diff --git a/aws/lambda_demo/urllib3/http2/probe.py b/aws/lambda_demo/urllib3/http2/probe.py new file mode 100644 index 000000000..9ea900764 --- /dev/null +++ b/aws/lambda_demo/urllib3/http2/probe.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import threading + + +class _HTTP2ProbeCache: + __slots__ = ( + "_lock", + "_cache_locks", + "_cache_values", + ) + + def __init__(self) -> None: + self._lock = threading.Lock() + self._cache_locks: dict[tuple[str, int], threading.RLock] = {} + self._cache_values: dict[tuple[str, int], bool | None] = {} + + def acquire_and_get(self, host: str, port: int) -> bool | None: + # By the end of this block we know that + # _cache_[values,locks] is available. + value = None + with self._lock: + key = (host, port) + try: + value = self._cache_values[key] + # If it's a known value we return right away. + if value is not None: + return value + except KeyError: + self._cache_locks[key] = threading.RLock() + self._cache_values[key] = None + + # If the value is unknown, we acquire the lock to signal + # to the requesting thread that the probe is in progress + # or that the current thread needs to return their findings. + key_lock = self._cache_locks[key] + key_lock.acquire() + try: + # If the by the time we get the lock the value has been + # updated we want to return the updated value. + value = self._cache_values[key] + + # In case an exception like KeyboardInterrupt is raised here. + except BaseException as e: # Defensive: + assert not isinstance(e, KeyError) # KeyError shouldn't be possible. + key_lock.release() + raise + + return value + + def set_and_release( + self, host: str, port: int, supports_http2: bool | None + ) -> None: + key = (host, port) + key_lock = self._cache_locks[key] + with key_lock: # Uses an RLock, so can be locked again from same thread. + if supports_http2 is None and self._cache_values[key] is not None: + raise ValueError( + "Cannot reset HTTP/2 support for origin after value has been set." + ) # Defensive: not expected in normal usage + + self._cache_values[key] = supports_http2 + key_lock.release() + + def _values(self) -> dict[tuple[str, int], bool | None]: + """This function is for testing purposes only. Gets the current state of the probe cache""" + with self._lock: + return {k: v for k, v in self._cache_values.items()} + + def _reset(self) -> None: + """This function is for testing purposes only. Reset the cache values""" + with self._lock: + self._cache_locks = {} + self._cache_values = {} + + +_HTTP2_PROBE_CACHE = _HTTP2ProbeCache() + +set_and_release = _HTTP2_PROBE_CACHE.set_and_release +acquire_and_get = _HTTP2_PROBE_CACHE.acquire_and_get +_values = _HTTP2_PROBE_CACHE._values +_reset = _HTTP2_PROBE_CACHE._reset + +__all__ = [ + "set_and_release", + "acquire_and_get", +] diff --git a/aws/lambda_demo/urllib3/poolmanager.py b/aws/lambda_demo/urllib3/poolmanager.py new file mode 100644 index 000000000..085d1dbaf --- /dev/null +++ b/aws/lambda_demo/urllib3/poolmanager.py @@ -0,0 +1,637 @@ +from __future__ import annotations + +import functools +import logging +import typing +import warnings +from types import TracebackType +from urllib.parse import urljoin + +from ._collections import HTTPHeaderDict, RecentlyUsedContainer +from ._request_methods import RequestMethods +from .connection import ProxyConfig +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme +from .exceptions import ( + LocationValueError, + MaxRetryError, + ProxySchemeUnknown, + URLSchemeUnknown, +) +from .response import BaseHTTPResponse +from .util.connection import _TYPE_SOCKET_OPTIONS +from .util.proxy import connection_requires_http_tunnel +from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import Url, parse_url + +if typing.TYPE_CHECKING: + import ssl + + from typing_extensions import Self + +__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] + + +log = logging.getLogger(__name__) + +SSL_KEYWORDS = ( + "key_file", + "cert_file", + "cert_reqs", + "ca_certs", + "ca_cert_data", + "ssl_version", + "ssl_minimum_version", + "ssl_maximum_version", + "ca_cert_dir", + "ssl_context", + "key_password", + "server_hostname", +) +# Default value for `blocksize` - a new parameter introduced to +# http.client.HTTPConnection & http.client.HTTPSConnection in Python 3.7 +_DEFAULT_BLOCKSIZE = 16384 + + +class PoolKey(typing.NamedTuple): + """ + All known keyword arguments that could be provided to the pool manager, its + pools, or the underlying connections. + + All custom key schemes should include the fields in this key at a minimum. + """ + + key_scheme: str + key_host: str + key_port: int | None + key_timeout: Timeout | float | int | None + key_retries: Retry | bool | int | None + key_block: bool | None + key_source_address: tuple[str, int] | None + key_key_file: str | None + key_key_password: str | None + key_cert_file: str | None + key_cert_reqs: str | None + key_ca_certs: str | None + key_ca_cert_data: str | bytes | None + key_ssl_version: int | str | None + key_ssl_minimum_version: ssl.TLSVersion | None + key_ssl_maximum_version: ssl.TLSVersion | None + key_ca_cert_dir: str | None + key_ssl_context: ssl.SSLContext | None + key_maxsize: int | None + key_headers: frozenset[tuple[str, str]] | None + key__proxy: Url | None + key__proxy_headers: frozenset[tuple[str, str]] | None + key__proxy_config: ProxyConfig | None + key_socket_options: _TYPE_SOCKET_OPTIONS | None + key__socks_options: frozenset[tuple[str, str]] | None + key_assert_hostname: bool | str | None + key_assert_fingerprint: str | None + key_server_hostname: str | None + key_blocksize: int | None + + +def _default_key_normalizer( + key_class: type[PoolKey], request_context: dict[str, typing.Any] +) -> PoolKey: + """ + Create a pool key out of a request context dictionary. + + According to RFC 3986, both the scheme and host are case-insensitive. + Therefore, this function normalizes both before constructing the pool + key for an HTTPS request. If you wish to change this behaviour, provide + alternate callables to ``key_fn_by_scheme``. + + :param key_class: + The class to use when constructing the key. This should be a namedtuple + with the ``scheme`` and ``host`` keys at a minimum. + :type key_class: namedtuple + :param request_context: + A dictionary-like object that contain the context for a request. + :type request_context: dict + + :return: A namedtuple that can be used as a connection pool key. + :rtype: PoolKey + """ + # Since we mutate the dictionary, make a copy first + context = request_context.copy() + context["scheme"] = context["scheme"].lower() + context["host"] = context["host"].lower() + + # These are both dictionaries and need to be transformed into frozensets + for key in ("headers", "_proxy_headers", "_socks_options"): + if key in context and context[key] is not None: + context[key] = frozenset(context[key].items()) + + # The socket_options key may be a list and needs to be transformed into a + # tuple. + socket_opts = context.get("socket_options") + if socket_opts is not None: + context["socket_options"] = tuple(socket_opts) + + # Map the kwargs to the names in the namedtuple - this is necessary since + # namedtuples can't have fields starting with '_'. + for key in list(context.keys()): + context["key_" + key] = context.pop(key) + + # Default to ``None`` for keys missing from the context + for field in key_class._fields: + if field not in context: + context[field] = None + + # Default key_blocksize to _DEFAULT_BLOCKSIZE if missing from the context + if context.get("key_blocksize") is None: + context["key_blocksize"] = _DEFAULT_BLOCKSIZE + + return key_class(**context) + + +#: A dictionary that maps a scheme to a callable that creates a pool key. +#: This can be used to alter the way pool keys are constructed, if desired. +#: Each PoolManager makes a copy of this dictionary so they can be configured +#: globally here, or individually on the instance. +key_fn_by_scheme = { + "http": functools.partial(_default_key_normalizer, PoolKey), + "https": functools.partial(_default_key_normalizer, PoolKey), +} + +pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool} + + +class PoolManager(RequestMethods): + """ + Allows for arbitrary requests while transparently keeping track of + necessary connection pools for you. + + :param num_pools: + Number of connection pools to cache before discarding the least + recently used pool. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param \\**connection_pool_kw: + Additional parameters are used to create fresh + :class:`urllib3.connectionpool.ConnectionPool` instances. + + Example: + + .. code-block:: python + + import urllib3 + + http = urllib3.PoolManager(num_pools=2) + + resp1 = http.request("GET", "https://google.com/") + resp2 = http.request("GET", "https://google.com/mail") + resp3 = http.request("GET", "https://yahoo.com/") + + print(len(http.pools)) + # 2 + + """ + + proxy: Url | None = None + proxy_config: ProxyConfig | None = None + + def __init__( + self, + num_pools: int = 10, + headers: typing.Mapping[str, str] | None = None, + **connection_pool_kw: typing.Any, + ) -> None: + super().__init__(headers) + self.connection_pool_kw = connection_pool_kw + + self.pools: RecentlyUsedContainer[PoolKey, HTTPConnectionPool] + self.pools = RecentlyUsedContainer(num_pools) + + # Locally set the pool classes and keys so other PoolManagers can + # override them. + self.pool_classes_by_scheme = pool_classes_by_scheme + self.key_fn_by_scheme = key_fn_by_scheme.copy() + + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> typing.Literal[False]: + self.clear() + # Return False to re-raise any potential exceptions + return False + + def _new_pool( + self, + scheme: str, + host: str, + port: int, + request_context: dict[str, typing.Any] | None = None, + ) -> HTTPConnectionPool: + """ + Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and + any additional pool keyword arguments. + + If ``request_context`` is provided, it is provided as keyword arguments + to the pool class used. This method is used to actually create the + connection pools handed out by :meth:`connection_from_url` and + companion methods. It is intended to be overridden for customization. + """ + pool_cls: type[HTTPConnectionPool] = self.pool_classes_by_scheme[scheme] + if request_context is None: + request_context = self.connection_pool_kw.copy() + + # Default blocksize to _DEFAULT_BLOCKSIZE if missing or explicitly + # set to 'None' in the request_context. + if request_context.get("blocksize") is None: + request_context["blocksize"] = _DEFAULT_BLOCKSIZE + + # Although the context has everything necessary to create the pool, + # this function has historically only used the scheme, host, and port + # in the positional args. When an API change is acceptable these can + # be removed. + for key in ("scheme", "host", "port"): + request_context.pop(key, None) + + if scheme == "http": + for kw in SSL_KEYWORDS: + request_context.pop(kw, None) + + return pool_cls(host, port, **request_context) + + def clear(self) -> None: + """ + Empty our store of pools and direct them all to close. + + This will not affect in-flight connections, but they will not be + re-used after completion. + """ + self.pools.clear() + + def connection_from_host( + self, + host: str | None, + port: int | None = None, + scheme: str | None = "http", + pool_kwargs: dict[str, typing.Any] | None = None, + ) -> HTTPConnectionPool: + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme. + + If ``port`` isn't given, it will be derived from the ``scheme`` using + ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is + provided, it is merged with the instance's ``connection_pool_kw`` + variable and used to create the new connection pool, if one is + needed. + """ + + if not host: + raise LocationValueError("No host specified.") + + request_context = self._merge_pool_kwargs(pool_kwargs) + request_context["scheme"] = scheme or "http" + if not port: + port = port_by_scheme.get(request_context["scheme"].lower(), 80) + request_context["port"] = port + request_context["host"] = host + + return self.connection_from_context(request_context) + + def connection_from_context( + self, request_context: dict[str, typing.Any] + ) -> HTTPConnectionPool: + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context. + + ``request_context`` must at least contain the ``scheme`` key and its + value must be a key in ``key_fn_by_scheme`` instance variable. + """ + if "strict" in request_context: + warnings.warn( + "The 'strict' parameter is no longer needed on Python 3+. " + "This will raise an error in urllib3 v2.1.0.", + DeprecationWarning, + ) + request_context.pop("strict") + + scheme = request_context["scheme"].lower() + pool_key_constructor = self.key_fn_by_scheme.get(scheme) + if not pool_key_constructor: + raise URLSchemeUnknown(scheme) + pool_key = pool_key_constructor(request_context) + + return self.connection_from_pool_key(pool_key, request_context=request_context) + + def connection_from_pool_key( + self, pool_key: PoolKey, request_context: dict[str, typing.Any] + ) -> HTTPConnectionPool: + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key. + + ``pool_key`` should be a namedtuple that only contains immutable + objects. At a minimum it must have the ``scheme``, ``host``, and + ``port`` fields. + """ + with self.pools.lock: + # If the scheme, host, or port doesn't match existing open + # connections, open a new ConnectionPool. + pool = self.pools.get(pool_key) + if pool: + return pool + + # Make a fresh ConnectionPool of the desired type + scheme = request_context["scheme"] + host = request_context["host"] + port = request_context["port"] + pool = self._new_pool(scheme, host, port, request_context=request_context) + self.pools[pool_key] = pool + + return pool + + def connection_from_url( + self, url: str, pool_kwargs: dict[str, typing.Any] | None = None + ) -> HTTPConnectionPool: + """ + Similar to :func:`urllib3.connectionpool.connection_from_url`. + + If ``pool_kwargs`` is not provided and a new pool needs to be + constructed, ``self.connection_pool_kw`` is used to initialize + the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs`` + is provided, it is used instead. Note that if a new pool does not + need to be created for the request, the provided ``pool_kwargs`` are + not used. + """ + u = parse_url(url) + return self.connection_from_host( + u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs + ) + + def _merge_pool_kwargs( + self, override: dict[str, typing.Any] | None + ) -> dict[str, typing.Any]: + """ + Merge a dictionary of override values for self.connection_pool_kw. + + This does not modify self.connection_pool_kw and returns a new dict. + Any keys in the override dictionary with a value of ``None`` are + removed from the merged dictionary. + """ + base_pool_kwargs = self.connection_pool_kw.copy() + if override: + for key, value in override.items(): + if value is None: + try: + del base_pool_kwargs[key] + except KeyError: + pass + else: + base_pool_kwargs[key] = value + return base_pool_kwargs + + def _proxy_requires_url_absolute_form(self, parsed_url: Url) -> bool: + """ + Indicates if the proxy requires the complete destination URL in the + request. Normally this is only needed when not using an HTTP CONNECT + tunnel. + """ + if self.proxy is None: + return False + + return not connection_requires_http_tunnel( + self.proxy, self.proxy_config, parsed_url.scheme + ) + + def urlopen( # type: ignore[override] + self, method: str, url: str, redirect: bool = True, **kw: typing.Any + ) -> BaseHTTPResponse: + """ + Same as :meth:`urllib3.HTTPConnectionPool.urlopen` + with custom cross-host redirect logic and only sends the request-uri + portion of the ``url``. + + The given ``url`` parameter must be absolute, such that an appropriate + :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. + """ + u = parse_url(url) + + if u.scheme is None: + warnings.warn( + "URLs without a scheme (ie 'https://') are deprecated and will raise an error " + "in a future version of urllib3. To avoid this DeprecationWarning ensure all URLs " + "start with 'https://' or 'http://'. Read more in this issue: " + "https://github.com/urllib3/urllib3/issues/2920", + category=DeprecationWarning, + stacklevel=2, + ) + + conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) + + kw["assert_same_host"] = False + kw["redirect"] = False + + if "headers" not in kw: + kw["headers"] = self.headers + + if self._proxy_requires_url_absolute_form(u): + response = conn.urlopen(method, url, **kw) + else: + response = conn.urlopen(method, u.request_uri, **kw) + + redirect_location = redirect and response.get_redirect_location() + if not redirect_location: + return response + + # Support relative URLs for redirecting. + redirect_location = urljoin(url, redirect_location) + + if response.status == 303: + # Change the method according to RFC 9110, Section 15.4.4. + method = "GET" + # And lose the body not to transfer anything sensitive. + kw["body"] = None + kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change() + + retries = kw.get("retries") + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + + # Strip headers marked as unsafe to forward to the redirected location. + # Check remove_headers_on_redirect to avoid a potential network call within + # conn.is_same_host() which may use socket.gethostbyname() in the future. + if retries.remove_headers_on_redirect and not conn.is_same_host( + redirect_location + ): + new_headers = kw["headers"].copy() + for header in kw["headers"]: + if header.lower() in retries.remove_headers_on_redirect: + new_headers.pop(header, None) + kw["headers"] = new_headers + + try: + retries = retries.increment(method, url, response=response, _pool=conn) + except MaxRetryError: + if retries.raise_on_redirect: + response.drain_conn() + raise + return response + + kw["retries"] = retries + kw["redirect"] = redirect + + log.info("Redirecting %s -> %s", url, redirect_location) + + response.drain_conn() + return self.urlopen(method, redirect_location, **kw) + + +class ProxyManager(PoolManager): + """ + Behaves just like :class:`PoolManager`, but sends all requests through + the defined proxy, using the CONNECT method for HTTPS URLs. + + :param proxy_url: + The URL of the proxy to be used. + + :param proxy_headers: + A dictionary containing headers that will be sent to the proxy. In case + of HTTP they are being sent with each request, while in the + HTTPS/CONNECT case they are sent only once. Could be used for proxy + authentication. + + :param proxy_ssl_context: + The proxy SSL context is used to establish the TLS connection to the + proxy when using HTTPS proxies. + + :param use_forwarding_for_https: + (Defaults to False) If set to True will forward requests to the HTTPS + proxy to be made on behalf of the client instead of creating a TLS + tunnel via the CONNECT method. **Enabling this flag means that request + and response headers and content will be visible from the HTTPS proxy** + whereas tunneling keeps request and response headers and content + private. IP address, target hostname, SNI, and port are always visible + to an HTTPS proxy even when this flag is disabled. + + :param proxy_assert_hostname: + The hostname of the certificate to verify against. + + :param proxy_assert_fingerprint: + The fingerprint of the certificate to verify against. + + Example: + + .. code-block:: python + + import urllib3 + + proxy = urllib3.ProxyManager("https://localhost:3128/") + + resp1 = proxy.request("GET", "https://google.com/") + resp2 = proxy.request("GET", "https://httpbin.org/") + + print(len(proxy.pools)) + # 1 + + resp3 = proxy.request("GET", "https://httpbin.org/") + resp4 = proxy.request("GET", "https://twitter.com/") + + print(len(proxy.pools)) + # 3 + + """ + + def __init__( + self, + proxy_url: str, + num_pools: int = 10, + headers: typing.Mapping[str, str] | None = None, + proxy_headers: typing.Mapping[str, str] | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + use_forwarding_for_https: bool = False, + proxy_assert_hostname: None | str | typing.Literal[False] = None, + proxy_assert_fingerprint: str | None = None, + **connection_pool_kw: typing.Any, + ) -> None: + if isinstance(proxy_url, HTTPConnectionPool): + str_proxy_url = f"{proxy_url.scheme}://{proxy_url.host}:{proxy_url.port}" + else: + str_proxy_url = proxy_url + proxy = parse_url(str_proxy_url) + + if proxy.scheme not in ("http", "https"): + raise ProxySchemeUnknown(proxy.scheme) + + if not proxy.port: + port = port_by_scheme.get(proxy.scheme, 80) + proxy = proxy._replace(port=port) + + self.proxy = proxy + self.proxy_headers = proxy_headers or {} + self.proxy_ssl_context = proxy_ssl_context + self.proxy_config = ProxyConfig( + proxy_ssl_context, + use_forwarding_for_https, + proxy_assert_hostname, + proxy_assert_fingerprint, + ) + + connection_pool_kw["_proxy"] = self.proxy + connection_pool_kw["_proxy_headers"] = self.proxy_headers + connection_pool_kw["_proxy_config"] = self.proxy_config + + super().__init__(num_pools, headers, **connection_pool_kw) + + def connection_from_host( + self, + host: str | None, + port: int | None = None, + scheme: str | None = "http", + pool_kwargs: dict[str, typing.Any] | None = None, + ) -> HTTPConnectionPool: + if scheme == "https": + return super().connection_from_host( + host, port, scheme, pool_kwargs=pool_kwargs + ) + + return super().connection_from_host( + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs # type: ignore[union-attr] + ) + + def _set_proxy_headers( + self, url: str, headers: typing.Mapping[str, str] | None = None + ) -> typing.Mapping[str, str]: + """ + Sets headers needed by proxies: specifically, the Accept and Host + headers. Only sets headers not provided by the user. + """ + headers_ = {"Accept": "*/*"} + + netloc = parse_url(url).netloc + if netloc: + headers_["Host"] = netloc + + if headers: + headers_.update(headers) + return headers_ + + def urlopen( # type: ignore[override] + self, method: str, url: str, redirect: bool = True, **kw: typing.Any + ) -> BaseHTTPResponse: + "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." + u = parse_url(url) + if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme): + # For connections using HTTP CONNECT, httplib sets the necessary + # headers on the CONNECT to the proxy. If we're not using CONNECT, + # we'll definitely need to set 'Host' at the very least. + headers = kw.get("headers", self.headers) + kw["headers"] = self._set_proxy_headers(url, headers) + + return super().urlopen(method, url, redirect=redirect, **kw) + + +def proxy_from_url(url: str, **kw: typing.Any) -> ProxyManager: + return ProxyManager(proxy_url=url, **kw) diff --git a/aws/lambda_demo/urllib3/py.typed b/aws/lambda_demo/urllib3/py.typed new file mode 100644 index 000000000..5f3ea3d91 --- /dev/null +++ b/aws/lambda_demo/urllib3/py.typed @@ -0,0 +1,2 @@ +# Instruct type checkers to look for inline type annotations in this package. +# See PEP 561. diff --git a/aws/lambda_demo/urllib3/response.py b/aws/lambda_demo/urllib3/response.py new file mode 100644 index 000000000..66c6a687e --- /dev/null +++ b/aws/lambda_demo/urllib3/response.py @@ -0,0 +1,1278 @@ +from __future__ import annotations + +import collections +import io +import json as _json +import logging +import re +import socket +import sys +import typing +import warnings +import zlib +from contextlib import contextmanager +from http.client import HTTPMessage as _HttplibHTTPMessage +from http.client import HTTPResponse as _HttplibHTTPResponse +from socket import timeout as SocketTimeout + +if typing.TYPE_CHECKING: + from ._base_connection import BaseHTTPConnection + +try: + try: + import brotlicffi as brotli # type: ignore[import-not-found] + except ImportError: + import brotli # type: ignore[import-not-found] +except ImportError: + brotli = None + +try: + import zstandard as zstd +except (AttributeError, ImportError, ValueError): # Defensive: + HAS_ZSTD = False +else: + # The package 'zstandard' added the 'eof' property starting + # in v0.18.0 which we require to ensure a complete and + # valid zstd stream was fed into the ZstdDecoder. + # See: https://github.com/urllib3/urllib3/pull/2624 + _zstd_version = tuple( + map(int, re.search(r"^([0-9]+)\.([0-9]+)", zstd.__version__).groups()) # type: ignore[union-attr] + ) + if _zstd_version < (0, 18): # Defensive: + HAS_ZSTD = False + else: + HAS_ZSTD = True + +from . import util +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from .connection import BaseSSLError, HTTPConnection, HTTPException +from .exceptions import ( + BodyNotHttplibCompatible, + DecodeError, + HTTPError, + IncompleteRead, + InvalidChunkLength, + InvalidHeader, + ProtocolError, + ReadTimeoutError, + ResponseNotChunked, + SSLError, +) +from .util.response import is_fp_closed, is_response_to_head +from .util.retry import Retry + +if typing.TYPE_CHECKING: + from .connectionpool import HTTPConnectionPool + +log = logging.getLogger(__name__) + + +class ContentDecoder: + def decompress(self, data: bytes) -> bytes: + raise NotImplementedError() + + def flush(self) -> bytes: + raise NotImplementedError() + + +class DeflateDecoder(ContentDecoder): + def __init__(self) -> None: + self._first_try = True + self._data = b"" + self._obj = zlib.decompressobj() + + def decompress(self, data: bytes) -> bytes: + if not data: + return data + + if not self._first_try: + return self._obj.decompress(data) + + self._data += data + try: + decompressed = self._obj.decompress(data) + if decompressed: + self._first_try = False + self._data = None # type: ignore[assignment] + return decompressed + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: + return self.decompress(self._data) + finally: + self._data = None # type: ignore[assignment] + + def flush(self) -> bytes: + return self._obj.flush() + + +class GzipDecoderState: + FIRST_MEMBER = 0 + OTHER_MEMBERS = 1 + SWALLOW_DATA = 2 + + +class GzipDecoder(ContentDecoder): + def __init__(self) -> None: + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER + + def decompress(self, data: bytes) -> bytes: + ret = bytearray() + if self._state == GzipDecoderState.SWALLOW_DATA or not data: + return bytes(ret) + while True: + try: + ret += self._obj.decompress(data) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise + data = self._obj.unused_data + if not data: + return bytes(ret) + self._state = GzipDecoderState.OTHER_MEMBERS + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + + def flush(self) -> bytes: + return self._obj.flush() + + +if brotli is not None: + + class BrotliDecoder(ContentDecoder): + # Supports both 'brotlipy' and 'Brotli' packages + # since they share an import name. The top branches + # are for 'brotlipy' and bottom branches for 'Brotli' + def __init__(self) -> None: + self._obj = brotli.Decompressor() + if hasattr(self._obj, "decompress"): + setattr(self, "decompress", self._obj.decompress) + else: + setattr(self, "decompress", self._obj.process) + + def flush(self) -> bytes: + if hasattr(self._obj, "flush"): + return self._obj.flush() # type: ignore[no-any-return] + return b"" + + +if HAS_ZSTD: + + class ZstdDecoder(ContentDecoder): + def __init__(self) -> None: + self._obj = zstd.ZstdDecompressor().decompressobj() + + def decompress(self, data: bytes) -> bytes: + if not data: + return b"" + data_parts = [self._obj.decompress(data)] + while self._obj.eof and self._obj.unused_data: + unused_data = self._obj.unused_data + self._obj = zstd.ZstdDecompressor().decompressobj() + data_parts.append(self._obj.decompress(unused_data)) + return b"".join(data_parts) + + def flush(self) -> bytes: + ret = self._obj.flush() # note: this is a no-op + if not self._obj.eof: + raise DecodeError("Zstandard data is incomplete") + return ret + + +class MultiDecoder(ContentDecoder): + """ + From RFC7231: + If one or more encodings have been applied to a representation, the + sender that applied the encodings MUST generate a Content-Encoding + header field that lists the content codings in the order in which + they were applied. + """ + + def __init__(self, modes: str) -> None: + self._decoders = [_get_decoder(m.strip()) for m in modes.split(",")] + + def flush(self) -> bytes: + return self._decoders[0].flush() + + def decompress(self, data: bytes) -> bytes: + for d in reversed(self._decoders): + data = d.decompress(data) + return data + + +def _get_decoder(mode: str) -> ContentDecoder: + if "," in mode: + return MultiDecoder(mode) + + # According to RFC 9110 section 8.4.1.3, recipients should + # consider x-gzip equivalent to gzip + if mode in ("gzip", "x-gzip"): + return GzipDecoder() + + if brotli is not None and mode == "br": + return BrotliDecoder() + + if HAS_ZSTD and mode == "zstd": + return ZstdDecoder() + + return DeflateDecoder() + + +class BytesQueueBuffer: + """Memory-efficient bytes buffer + + To return decoded data in read() and still follow the BufferedIOBase API, we need a + buffer to always return the correct amount of bytes. + + This buffer should be filled using calls to put() + + Our maximum memory usage is determined by the sum of the size of: + + * self.buffer, which contains the full data + * the largest chunk that we will copy in get() + + The worst case scenario is a single chunk, in which case we'll make a full copy of + the data inside get(). + """ + + def __init__(self) -> None: + self.buffer: typing.Deque[bytes] = collections.deque() + self._size: int = 0 + + def __len__(self) -> int: + return self._size + + def put(self, data: bytes) -> None: + self.buffer.append(data) + self._size += len(data) + + def get(self, n: int) -> bytes: + if n == 0: + return b"" + elif not self.buffer: + raise RuntimeError("buffer is empty") + elif n < 0: + raise ValueError("n should be > 0") + + fetched = 0 + ret = io.BytesIO() + while fetched < n: + remaining = n - fetched + chunk = self.buffer.popleft() + chunk_length = len(chunk) + if remaining < chunk_length: + left_chunk, right_chunk = chunk[:remaining], chunk[remaining:] + ret.write(left_chunk) + self.buffer.appendleft(right_chunk) + self._size -= remaining + break + else: + ret.write(chunk) + self._size -= chunk_length + fetched += chunk_length + + if not self.buffer: + break + + return ret.getvalue() + + def get_all(self) -> bytes: + buffer = self.buffer + if not buffer: + assert self._size == 0 + return b"" + if len(buffer) == 1: + result = buffer.pop() + else: + ret = io.BytesIO() + ret.writelines(buffer.popleft() for _ in range(len(buffer))) + result = ret.getvalue() + self._size = 0 + return result + + +class BaseHTTPResponse(io.IOBase): + CONTENT_DECODERS = ["gzip", "x-gzip", "deflate"] + if brotli is not None: + CONTENT_DECODERS += ["br"] + if HAS_ZSTD: + CONTENT_DECODERS += ["zstd"] + REDIRECT_STATUSES = [301, 302, 303, 307, 308] + + DECODER_ERROR_CLASSES: tuple[type[Exception], ...] = (IOError, zlib.error) + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + + if HAS_ZSTD: + DECODER_ERROR_CLASSES += (zstd.ZstdError,) + + def __init__( + self, + *, + headers: typing.Mapping[str, str] | typing.Mapping[bytes, bytes] | None = None, + status: int, + version: int, + version_string: str, + reason: str | None, + decode_content: bool, + request_url: str | None, + retries: Retry | None = None, + ) -> None: + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) # type: ignore[arg-type] + self.status = status + self.version = version + self.version_string = version_string + self.reason = reason + self.decode_content = decode_content + self._has_decoded_content = False + self._request_url: str | None = request_url + self.retries = retries + + self.chunked = False + tr_enc = self.headers.get("transfer-encoding", "").lower() + # Don't incur the penalty of creating a list and then discarding it + encodings = (enc.strip() for enc in tr_enc.split(",")) + if "chunked" in encodings: + self.chunked = True + + self._decoder: ContentDecoder | None = None + self.length_remaining: int | None + + def get_redirect_location(self) -> str | None | typing.Literal[False]: + """ + Should we redirect and where to? + + :returns: Truthy redirect location string if we got a redirect status + code and valid location. ``None`` if redirect status and no + location. ``False`` if not a redirect status code. + """ + if self.status in self.REDIRECT_STATUSES: + return self.headers.get("location") + return False + + @property + def data(self) -> bytes: + raise NotImplementedError() + + def json(self) -> typing.Any: + """ + Deserializes the body of the HTTP response as a Python object. + + The body of the HTTP response must be encoded using UTF-8, as per + `RFC 8529 Section 8.1 `_. + + To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to + your custom decoder instead. + + If the body of the HTTP response is not decodable to UTF-8, a + `UnicodeDecodeError` will be raised. If the body of the HTTP response is not a + valid JSON document, a `json.JSONDecodeError` will be raised. + + Read more :ref:`here `. + + :returns: The body of the HTTP response as a Python object. + """ + data = self.data.decode("utf-8") + return _json.loads(data) + + @property + def url(self) -> str | None: + raise NotImplementedError() + + @url.setter + def url(self, url: str | None) -> None: + raise NotImplementedError() + + @property + def connection(self) -> BaseHTTPConnection | None: + raise NotImplementedError() + + @property + def retries(self) -> Retry | None: + return self._retries + + @retries.setter + def retries(self, retries: Retry | None) -> None: + # Override the request_url if retries has a redirect location. + if retries is not None and retries.history: + self.url = retries.history[-1].redirect_location + self._retries = retries + + def stream( + self, amt: int | None = 2**16, decode_content: bool | None = None + ) -> typing.Iterator[bytes]: + raise NotImplementedError() + + def read( + self, + amt: int | None = None, + decode_content: bool | None = None, + cache_content: bool = False, + ) -> bytes: + raise NotImplementedError() + + def read1( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> bytes: + raise NotImplementedError() + + def read_chunked( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> typing.Iterator[bytes]: + raise NotImplementedError() + + def release_conn(self) -> None: + raise NotImplementedError() + + def drain_conn(self) -> None: + raise NotImplementedError() + + def shutdown(self) -> None: + raise NotImplementedError() + + def close(self) -> None: + raise NotImplementedError() + + def _init_decoder(self) -> None: + """ + Set-up the _decoder attribute if necessary. + """ + # Note: content-encoding value should be case-insensitive, per RFC 7230 + # Section 3.2 + content_encoding = self.headers.get("content-encoding", "").lower() + if self._decoder is None: + if content_encoding in self.CONTENT_DECODERS: + self._decoder = _get_decoder(content_encoding) + elif "," in content_encoding: + encodings = [ + e.strip() + for e in content_encoding.split(",") + if e.strip() in self.CONTENT_DECODERS + ] + if encodings: + self._decoder = _get_decoder(content_encoding) + + def _decode( + self, data: bytes, decode_content: bool | None, flush_decoder: bool + ) -> bytes: + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + if self._has_decoded_content: + raise RuntimeError( + "Calling read(decode_content=False) is not supported after " + "read(decode_content=True) was called." + ) + return data + + try: + if self._decoder: + data = self._decoder.decompress(data) + self._has_decoded_content = True + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( + "Received response with content-encoding: %s, but " + "failed to decode it." % content_encoding, + e, + ) from e + if flush_decoder: + data += self._flush_decoder() + + return data + + def _flush_decoder(self) -> bytes: + """ + Flushes the decoder. Should only be called if the decoder is actually + being used. + """ + if self._decoder: + return self._decoder.decompress(b"") + self._decoder.flush() + return b"" + + # Compatibility methods for `io` module + def readinto(self, b: bytearray) -> int: + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[: len(temp)] = temp + return len(temp) + + # Compatibility methods for http.client.HTTPResponse + def getheaders(self) -> HTTPHeaderDict: + warnings.warn( + "HTTPResponse.getheaders() is deprecated and will be removed " + "in urllib3 v2.1.0. Instead access HTTPResponse.headers directly.", + category=DeprecationWarning, + stacklevel=2, + ) + return self.headers + + def getheader(self, name: str, default: str | None = None) -> str | None: + warnings.warn( + "HTTPResponse.getheader() is deprecated and will be removed " + "in urllib3 v2.1.0. Instead use HTTPResponse.headers.get(name, default).", + category=DeprecationWarning, + stacklevel=2, + ) + return self.headers.get(name, default) + + # Compatibility method for http.cookiejar + def info(self) -> HTTPHeaderDict: + return self.headers + + def geturl(self) -> str | None: + return self.url + + +class HTTPResponse(BaseHTTPResponse): + """ + HTTP Response container. + + Backwards-compatible with :class:`http.client.HTTPResponse` but the response ``body`` is + loaded and decoded on-demand when the ``data`` property is accessed. This + class is also compatible with the Python standard library's :mod:`io` + module, and can hence be treated as a readable object in the context of that + framework. + + Extra parameters for behaviour not present in :class:`http.client.HTTPResponse`: + + :param preload_content: + If True, the response's body will be preloaded during construction. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param original_response: + When this HTTPResponse wrapper is generated from an :class:`http.client.HTTPResponse` + object, it's convenient to include the original for debug purposes. It's + otherwise unused. + + :param retries: + The retries contains the last :class:`~urllib3.util.retry.Retry` that + was used during the request. + + :param enforce_content_length: + Enforce content length checking. Body returned by server must match + value of Content-Length header, if present. Otherwise, raise error. + """ + + def __init__( + self, + body: _TYPE_BODY = "", + headers: typing.Mapping[str, str] | typing.Mapping[bytes, bytes] | None = None, + status: int = 0, + version: int = 0, + version_string: str = "HTTP/?", + reason: str | None = None, + preload_content: bool = True, + decode_content: bool = True, + original_response: _HttplibHTTPResponse | None = None, + pool: HTTPConnectionPool | None = None, + connection: HTTPConnection | None = None, + msg: _HttplibHTTPMessage | None = None, + retries: Retry | None = None, + enforce_content_length: bool = True, + request_method: str | None = None, + request_url: str | None = None, + auto_close: bool = True, + sock_shutdown: typing.Callable[[int], None] | None = None, + ) -> None: + super().__init__( + headers=headers, + status=status, + version=version, + version_string=version_string, + reason=reason, + decode_content=decode_content, + request_url=request_url, + retries=retries, + ) + + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close + + self._body = None + self._fp: _HttplibHTTPResponse | None = None + self._original_response = original_response + self._fp_bytes_read = 0 + self.msg = msg + + if body and isinstance(body, (str, bytes)): + self._body = body + + self._pool = pool + self._connection = connection + + if hasattr(body, "read"): + self._fp = body # type: ignore[assignment] + self._sock_shutdown = sock_shutdown + + # Are we using the chunked-style of transfer encoding? + self.chunk_left: int | None = None + + # Determine length of response + self.length_remaining = self._init_length(request_method) + + # Used to return the correct amount of bytes for partial read()s + self._decoded_buffer = BytesQueueBuffer() + + # If requested, preload the body. + if preload_content and not self._body: + self._body = self.read(decode_content=decode_content) + + def release_conn(self) -> None: + if not self._pool or not self._connection: + return None + + self._pool._put_conn(self._connection) + self._connection = None + + def drain_conn(self) -> None: + """ + Read and discard any remaining HTTP response data in the response connection. + + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: + self.read() + except (HTTPError, OSError, BaseSSLError, HTTPException): + pass + + @property + def data(self) -> bytes: + # For backwards-compat with earlier urllib3 0.4 and earlier. + if self._body: + return self._body # type: ignore[return-value] + + if self._fp: + return self.read(cache_content=True) + + return None # type: ignore[return-value] + + @property + def connection(self) -> HTTPConnection | None: + return self._connection + + def isclosed(self) -> bool: + return is_fp_closed(self._fp) + + def tell(self) -> int: + """ + Obtain the number of bytes pulled over the wire so far. May differ from + the amount of content returned by :meth:``urllib3.response.HTTPResponse.read`` + if bytes are encoded on the wire (e.g, compressed). + """ + return self._fp_bytes_read + + def _init_length(self, request_method: str | None) -> int | None: + """ + Set initial length value for Response content if available. + """ + length: int | None + content_length: str | None = self.headers.get("content-length") + + if content_length is not None: + if self.chunked: + # This Response will fail with an IncompleteRead if it can't be + # received as chunked. This method falls back to attempt reading + # the response before raising an exception. + log.warning( + "Received response with both Content-Length and " + "Transfer-Encoding set. This is expressly forbidden " + "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " + "attempting to process response as Transfer-Encoding: " + "chunked." + ) + return None + + try: + # RFC 7230 section 3.3.2 specifies multiple content lengths can + # be sent in a single Content-Length header + # (e.g. Content-Length: 42, 42). This line ensures the values + # are all valid ints and that as long as the `set` length is 1, + # all values are the same. Otherwise, the header is invalid. + lengths = {int(val) for val in content_length.split(",")} + if len(lengths) > 1: + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % content_length + ) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + else: # if content_length is None + length = None + + # Convert status to int for comparison + # In some cases, httplib returns a status of "_UNKNOWN" + try: + status = int(self.status) + except ValueError: + status = 0 + + # Check for responses that shouldn't include a body + if status in (204, 304) or 100 <= status < 200 or request_method == "HEAD": + length = 0 + + return length + + @contextmanager + def _error_catcher(self) -> typing.Generator[None]: + """ + Catch low-level python exceptions, instead re-raising urllib3 + variants, so that low-level exceptions are not leaked in the + high-level api. + + On exit, release the connection back to the pool. + """ + clean_exit = False + + try: + try: + yield + + except SocketTimeout as e: + # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but + # there is yet no clean way to get at it from this context. + raise ReadTimeoutError(self._pool, None, "Read timed out.") from e # type: ignore[arg-type] + + except BaseSSLError as e: + # FIXME: Is there a better way to differentiate between SSLErrors? + if "read operation timed out" not in str(e): + # SSL errors related to framing/MAC get wrapped and reraised here + raise SSLError(e) from e + + raise ReadTimeoutError(self._pool, None, "Read timed out.") from e # type: ignore[arg-type] + + except IncompleteRead as e: + if ( + e.expected is not None + and e.partial is not None + and e.expected == -e.partial + ): + arg = "Response may not contain content." + else: + arg = f"Connection broken: {e!r}" + raise ProtocolError(arg, e) from e + + except (HTTPException, OSError) as e: + raise ProtocolError(f"Connection broken: {e!r}", e) from e + + # If no exception is thrown, we should avoid cleaning up + # unnecessarily. + clean_exit = True + finally: + # If we didn't terminate cleanly, we need to throw away our + # connection. + if not clean_exit: + # The response may not be closed but we're not going to use it + # anymore so close it now to ensure that the connection is + # released back to the pool. + if self._original_response: + self._original_response.close() + + # Closing the response may not actually be sufficient to close + # everything, so if we have a hold of the connection close that + # too. + if self._connection: + self._connection.close() + + # If we hold the original response but it's closed now, we should + # return the connection back to the pool. + if self._original_response and self._original_response.isclosed(): + self.release_conn() + + def _fp_read( + self, + amt: int | None = None, + *, + read1: bool = False, + ) -> bytes: + """ + Read a response with the thought that reading the number of bytes + larger than can fit in a 32-bit int at a time via SSL in some + known cases leads to an overflow error that has to be prevented + if `amt` or `self.length_remaining` indicate that a problem may + happen. + + The known cases: + * CPython < 3.9.7 because of a bug + https://github.com/urllib3/urllib3/issues/2513#issuecomment-1152559900. + * urllib3 injected with pyOpenSSL-backed SSL-support. + * CPython < 3.10 only when `amt` does not fit 32-bit int. + """ + assert self._fp + c_int_max = 2**31 - 1 + if ( + (amt and amt > c_int_max) + or ( + amt is None + and self.length_remaining + and self.length_remaining > c_int_max + ) + ) and (util.IS_PYOPENSSL or sys.version_info < (3, 10)): + if read1: + return self._fp.read1(c_int_max) + buffer = io.BytesIO() + # Besides `max_chunk_amt` being a maximum chunk size, it + # affects memory overhead of reading a response by this + # method in CPython. + # `c_int_max` equal to 2 GiB - 1 byte is the actual maximum + # chunk size that does not lead to an overflow error, but + # 256 MiB is a compromise. + max_chunk_amt = 2**28 + while amt is None or amt != 0: + if amt is not None: + chunk_amt = min(amt, max_chunk_amt) + amt -= chunk_amt + else: + chunk_amt = max_chunk_amt + data = self._fp.read(chunk_amt) + if not data: + break + buffer.write(data) + del data # to reduce peak memory usage by `max_chunk_amt`. + return buffer.getvalue() + elif read1: + return self._fp.read1(amt) if amt is not None else self._fp.read1() + else: + # StringIO doesn't like amt=None + return self._fp.read(amt) if amt is not None else self._fp.read() + + def _raw_read( + self, + amt: int | None = None, + *, + read1: bool = False, + ) -> bytes: + """ + Reads `amt` of bytes from the socket. + """ + if self._fp is None: + return None # type: ignore[return-value] + + fp_closed = getattr(self._fp, "closed", False) + + with self._error_catcher(): + data = self._fp_read(amt, read1=read1) if not fp_closed else b"" + if amt is not None and amt != 0 and not data: + # Platform-specific: Buggy versions of Python. + # Close the connection when no data is returned + # + # This is redundant to what httplib/http.client _should_ + # already do. However, versions of python released before + # December 15, 2012 (http://bugs.python.org/issue16298) do + # not properly close the connection in all cases. There is + # no harm in redundantly calling close. + self._fp.close() + if ( + self.enforce_content_length + and self.length_remaining is not None + and self.length_remaining != 0 + ): + # This is an edge case that httplib failed to cover due + # to concerns of backward compatibility. We're + # addressing it here to make sure IncompleteRead is + # raised during streaming, so all calls with incorrect + # Content-Length are caught. + raise IncompleteRead(self._fp_bytes_read, self.length_remaining) + elif read1 and ( + (amt != 0 and not data) or self.length_remaining == len(data) + ): + # All data has been read, but `self._fp.read1` in + # CPython 3.12 and older doesn't always close + # `http.client.HTTPResponse`, so we close it here. + # See https://github.com/python/cpython/issues/113199 + self._fp.close() + + if data: + self._fp_bytes_read += len(data) + if self.length_remaining is not None: + self.length_remaining -= len(data) + return data + + def read( + self, + amt: int | None = None, + decode_content: bool | None = None, + cache_content: bool = False, + ) -> bytes: + """ + Similar to :meth:`http.client.HTTPResponse.read`, but with two additional + parameters: ``decode_content`` and ``cache_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param cache_content: + If True, will save the returned data such that the same result is + returned despite of the state of the underlying file object. This + is useful if you want the ``.data`` property to continue working + after having ``.read()`` the file object. (Overridden if ``amt`` is + set.) + """ + self._init_decoder() + if decode_content is None: + decode_content = self.decode_content + + if amt and amt < 0: + # Negative numbers and `None` should be treated the same. + amt = None + elif amt is not None: + cache_content = False + + if len(self._decoded_buffer) >= amt: + return self._decoded_buffer.get(amt) + + data = self._raw_read(amt) + + flush_decoder = amt is None or (amt != 0 and not data) + + if not data and len(self._decoded_buffer) == 0: + return data + + if amt is None: + data = self._decode(data, decode_content, flush_decoder) + if cache_content: + self._body = data + else: + # do not waste memory on buffer when not decoding + if not decode_content: + if self._has_decoded_content: + raise RuntimeError( + "Calling read(decode_content=False) is not supported after " + "read(decode_content=True) was called." + ) + return data + + decoded_data = self._decode(data, decode_content, flush_decoder) + self._decoded_buffer.put(decoded_data) + + while len(self._decoded_buffer) < amt and data: + # TODO make sure to initially read enough data to get past the headers + # For example, the GZ file header takes 10 bytes, we don't want to read + # it one byte at a time + data = self._raw_read(amt) + decoded_data = self._decode(data, decode_content, flush_decoder) + self._decoded_buffer.put(decoded_data) + data = self._decoded_buffer.get(amt) + + return data + + def read1( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> bytes: + """ + Similar to ``http.client.HTTPResponse.read1`` and documented + in :meth:`io.BufferedReader.read1`, but with an additional parameter: + ``decode_content``. + + :param amt: + How much of the content to read. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if decode_content is None: + decode_content = self.decode_content + if amt and amt < 0: + # Negative numbers and `None` should be treated the same. + amt = None + # try and respond without going to the network + if self._has_decoded_content: + if not decode_content: + raise RuntimeError( + "Calling read1(decode_content=False) is not supported after " + "read1(decode_content=True) was called." + ) + if len(self._decoded_buffer) > 0: + if amt is None: + return self._decoded_buffer.get_all() + return self._decoded_buffer.get(amt) + if amt == 0: + return b"" + + # FIXME, this method's type doesn't say returning None is possible + data = self._raw_read(amt, read1=True) + if not decode_content or data is None: + return data + + self._init_decoder() + while True: + flush_decoder = not data + decoded_data = self._decode(data, decode_content, flush_decoder) + self._decoded_buffer.put(decoded_data) + if decoded_data or flush_decoder: + break + data = self._raw_read(8192, read1=True) + + if amt is None: + return self._decoded_buffer.get_all() + return self._decoded_buffer.get(amt) + + def stream( + self, amt: int | None = 2**16, decode_content: bool | None = None + ) -> typing.Generator[bytes]: + """ + A generator wrapper for the read() method. A call will block until + ``amt`` bytes have been read from the connection or until the + connection is closed. + + :param amt: + How much of the content to read. The generator will return up to + much data per iteration, but may return less. This is particularly + likely when using compressed data. However, the empty string will + never be returned. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if self.chunked and self.supports_chunked_reads(): + yield from self.read_chunked(amt, decode_content=decode_content) + else: + while not is_fp_closed(self._fp) or len(self._decoded_buffer) > 0: + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + + # Overrides from io.IOBase + def readable(self) -> bool: + return True + + def shutdown(self) -> None: + if not self._sock_shutdown: + raise ValueError("Cannot shutdown socket as self._sock_shutdown is not set") + self._sock_shutdown(socket.SHUT_RD) + + def close(self) -> None: + self._sock_shutdown = None + + if not self.closed and self._fp: + self._fp.close() + + if self._connection: + self._connection.close() + + if not self.auto_close: + io.IOBase.close(self) + + @property + def closed(self) -> bool: + if not self.auto_close: + return io.IOBase.closed.__get__(self) # type: ignore[no-any-return] + elif self._fp is None: + return True + elif hasattr(self._fp, "isclosed"): + return self._fp.isclosed() + elif hasattr(self._fp, "closed"): + return self._fp.closed + else: + return True + + def fileno(self) -> int: + if self._fp is None: + raise OSError("HTTPResponse has no file to get a fileno from") + elif hasattr(self._fp, "fileno"): + return self._fp.fileno() + else: + raise OSError( + "The file-like object this HTTPResponse is wrapped " + "around has no file descriptor" + ) + + def flush(self) -> None: + if ( + self._fp is not None + and hasattr(self._fp, "flush") + and not getattr(self._fp, "closed", False) + ): + return self._fp.flush() + + def supports_chunked_reads(self) -> bool: + """ + Checks if the underlying file-like object looks like a + :class:`http.client.HTTPResponse` object. We do this by testing for + the fp attribute. If it is present we assume it returns raw chunks as + processed by read_chunked(). + """ + return hasattr(self._fp, "fp") + + def _update_chunk_length(self) -> None: + # First, we'll figure out length of a chunk and then + # we'll try to read it from socket. + if self.chunk_left is not None: + return None + line = self._fp.fp.readline() # type: ignore[union-attr] + line = line.split(b";", 1)[0] + try: + self.chunk_left = int(line, 16) + except ValueError: + self.close() + if line: + # Invalid chunked protocol response, abort. + raise InvalidChunkLength(self, line) from None + else: + # Truncated at start of next chunk + raise ProtocolError("Response ended prematurely") from None + + def _handle_chunk(self, amt: int | None) -> bytes: + returned_chunk = None + if amt is None: + chunk = self._fp._safe_read(self.chunk_left) # type: ignore[union-attr] + returned_chunk = chunk + self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + self.chunk_left = None + elif self.chunk_left is not None and amt < self.chunk_left: + value = self._fp._safe_read(amt) # type: ignore[union-attr] + self.chunk_left = self.chunk_left - amt + returned_chunk = value + elif amt == self.chunk_left: + value = self._fp._safe_read(amt) # type: ignore[union-attr] + self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + self.chunk_left = None + returned_chunk = value + else: # amt > self.chunk_left + returned_chunk = self._fp._safe_read(self.chunk_left) # type: ignore[union-attr] + self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + self.chunk_left = None + return returned_chunk # type: ignore[no-any-return] + + def read_chunked( + self, amt: int | None = None, decode_content: bool | None = None + ) -> typing.Generator[bytes]: + """ + Similar to :meth:`HTTPResponse.read`, but with an additional + parameter: ``decode_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + self._init_decoder() + # FIXME: Rewrite this method and make it a class with a better structured logic. + if not self.chunked: + raise ResponseNotChunked( + "Response is not chunked. " + "Header 'transfer-encoding: chunked' is missing." + ) + if not self.supports_chunked_reads(): + raise BodyNotHttplibCompatible( + "Body should be http.client.HTTPResponse like. " + "It should have have an fp attribute which returns raw chunks." + ) + + with self._error_catcher(): + # Don't bother reading the body of a HEAD request. + if self._original_response and is_response_to_head(self._original_response): + self._original_response.close() + return None + + # If a response is already read and closed + # then return immediately. + if self._fp.fp is None: # type: ignore[union-attr] + return None + + if amt and amt < 0: + # Negative numbers and `None` should be treated the same, + # but httplib handles only `None` correctly. + amt = None + + while True: + self._update_chunk_length() + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) + decoded = self._decode( + chunk, decode_content=decode_content, flush_decoder=False + ) + if decoded: + yield decoded + + if decode_content: + # On CPython and PyPy, we should never need to flush the + # decoder. However, on Jython we *might* need to, so + # lets defensively do it anyway. + decoded = self._flush_decoder() + if decoded: # Platform-specific: Jython. + yield decoded + + # Chunk content ends with \r\n: discard it. + while self._fp is not None: + line = self._fp.fp.readline() + if not line: + # Some sites may not end with '\r\n'. + break + if line == b"\r\n": + break + + # We read everything; close the "file". + if self._original_response: + self._original_response.close() + + @property + def url(self) -> str | None: + """ + Returns the URL that was the source of this response. + If the request that generated this response redirected, this method + will return the final redirect location. + """ + return self._request_url + + @url.setter + def url(self, url: str) -> None: + self._request_url = url + + def __iter__(self) -> typing.Iterator[bytes]: + buffer: list[bytes] = [] + for chunk in self.stream(decode_content=True): + if b"\n" in chunk: + chunks = chunk.split(b"\n") + yield b"".join(buffer) + chunks[0] + b"\n" + for x in chunks[1:-1]: + yield x + b"\n" + if chunks[-1]: + buffer = [chunks[-1]] + else: + buffer = [] + else: + buffer.append(chunk) + if buffer: + yield b"".join(buffer) diff --git a/aws/lambda_demo/urllib3/util/__init__.py b/aws/lambda_demo/urllib3/util/__init__.py new file mode 100644 index 000000000..534126033 --- /dev/null +++ b/aws/lambda_demo/urllib3/util/__init__.py @@ -0,0 +1,42 @@ +# For backwards compatibility, provide imports that used to be here. +from __future__ import annotations + +from .connection import is_connection_dropped +from .request import SKIP_HEADER, SKIPPABLE_HEADERS, make_headers +from .response import is_fp_closed +from .retry import Retry +from .ssl_ import ( + ALPN_PROTOCOLS, + IS_PYOPENSSL, + SSLContext, + assert_fingerprint, + create_urllib3_context, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .timeout import Timeout +from .url import Url, parse_url +from .wait import wait_for_read, wait_for_write + +__all__ = ( + "IS_PYOPENSSL", + "SSLContext", + "ALPN_PROTOCOLS", + "Retry", + "Timeout", + "Url", + "assert_fingerprint", + "create_urllib3_context", + "is_connection_dropped", + "is_fp_closed", + "parse_url", + "make_headers", + "resolve_cert_reqs", + "resolve_ssl_version", + "ssl_wrap_socket", + "wait_for_read", + "wait_for_write", + "SKIP_HEADER", + "SKIPPABLE_HEADERS", +) diff --git a/aws/lambda_demo/urllib3/util/connection.py b/aws/lambda_demo/urllib3/util/connection.py new file mode 100644 index 000000000..f92519ee9 --- /dev/null +++ b/aws/lambda_demo/urllib3/util/connection.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +import socket +import typing + +from ..exceptions import LocationParseError +from .timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT + +_TYPE_SOCKET_OPTIONS = list[tuple[int, int, typing.Union[int, bytes]]] + +if typing.TYPE_CHECKING: + from .._base_connection import BaseHTTPConnection + + +def is_connection_dropped(conn: BaseHTTPConnection) -> bool: # Platform-specific + """ + Returns True if the connection is dropped and should be closed. + :param conn: :class:`urllib3.connection.HTTPConnection` object. + """ + return not conn.is_connected + + +# This function is copied from socket.py in the Python 2.7 standard +# library test suite. Added to its signature is only `socket_options`. +# One additional modification is that we avoid binding to IPv6 servers +# discovered in DNS if the system doesn't have IPv6 functionality. +def create_connection( + address: tuple[str, int], + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + socket_options: _TYPE_SOCKET_OPTIONS | None = None, +) -> socket.socket: + """Connect to *address* and return the socket object. + + Convenience function. Connect to *address* (a 2-tuple ``(host, + port)``) and return the socket object. Passing the optional + *timeout* parameter will set the timeout on the socket instance + before attempting to connect. If no *timeout* is supplied, the + global default timeout setting returned by :func:`socket.getdefaulttimeout` + is used. If *source_address* is set it must be a tuple of (host, port) + for the socket to bind as a source address before making the connection. + An host of '' or port 0 tells the OS to use the default. + """ + + host, port = address + if host.startswith("["): + host = host.strip("[]") + err = None + + # Using the value from allowed_gai_family() in the context of getaddrinfo lets + # us select whether to work with IPv4 DNS records, IPv6 records, or both. + # The original create_connection function always returns all records. + family = allowed_gai_family() + + try: + host.encode("idna") + except UnicodeError: + raise LocationParseError(f"'{host}', label empty or too long") from None + + for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + + # If provided, set socket level options before connecting. + _set_socket_options(sock, socket_options) + + if timeout is not _DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + # Break explicitly a reference cycle + err = None + return sock + + except OSError as _: + err = _ + if sock is not None: + sock.close() + + if err is not None: + try: + raise err + finally: + # Break explicitly a reference cycle + err = None + else: + raise OSError("getaddrinfo returns an empty list") + + +def _set_socket_options( + sock: socket.socket, options: _TYPE_SOCKET_OPTIONS | None +) -> None: + if options is None: + return + + for opt in options: + sock.setsockopt(*opt) + + +def allowed_gai_family() -> socket.AddressFamily: + """This function is designed to work in the context of + getaddrinfo, where family=socket.AF_UNSPEC is the default and + will perform a DNS search for both IPv6 and IPv4 records.""" + + family = socket.AF_INET + if HAS_IPV6: + family = socket.AF_UNSPEC + return family + + +def _has_ipv6(host: str) -> bool: + """Returns True if the system can bind an IPv6 address.""" + sock = None + has_ipv6 = False + + if socket.has_ipv6: + # has_ipv6 returns true if cPython was compiled with IPv6 support. + # It does not tell us if the system has IPv6 support enabled. To + # determine that we must bind to an IPv6 address. + # https://github.com/urllib3/urllib3/pull/611 + # https://bugs.python.org/issue658327 + try: + sock = socket.socket(socket.AF_INET6) + sock.bind((host, 0)) + has_ipv6 = True + except Exception: + pass + + if sock: + sock.close() + return has_ipv6 + + +HAS_IPV6 = _has_ipv6("::1") diff --git a/aws/lambda_demo/urllib3/util/proxy.py b/aws/lambda_demo/urllib3/util/proxy.py new file mode 100644 index 000000000..908fc6621 --- /dev/null +++ b/aws/lambda_demo/urllib3/util/proxy.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import typing + +from .url import Url + +if typing.TYPE_CHECKING: + from ..connection import ProxyConfig + + +def connection_requires_http_tunnel( + proxy_url: Url | None = None, + proxy_config: ProxyConfig | None = None, + destination_scheme: str | None = None, +) -> bool: + """ + Returns True if the connection requires an HTTP CONNECT through the proxy. + + :param URL proxy_url: + URL of the proxy. + :param ProxyConfig proxy_config: + Proxy configuration from poolmanager.py + :param str destination_scheme: + The scheme of the destination. (i.e https, http, etc) + """ + # If we're not using a proxy, no way to use a tunnel. + if proxy_url is None: + return False + + # HTTP destinations never require tunneling, we always forward. + if destination_scheme == "http": + return False + + # Support for forwarding with HTTPS proxies and HTTPS destinations. + if ( + proxy_url.scheme == "https" + and proxy_config + and proxy_config.use_forwarding_for_https + ): + return False + + # Otherwise always use a tunnel. + return True diff --git a/aws/lambda_demo/urllib3/util/request.py b/aws/lambda_demo/urllib3/util/request.py new file mode 100644 index 000000000..94392a13b --- /dev/null +++ b/aws/lambda_demo/urllib3/util/request.py @@ -0,0 +1,258 @@ +from __future__ import annotations + +import io +import typing +from base64 import b64encode +from enum import Enum + +from ..exceptions import UnrewindableBodyError +from .util import to_bytes + +if typing.TYPE_CHECKING: + from typing import Final + +# Pass as a value within ``headers`` to skip +# emitting some HTTP headers that are added automatically. +# The only headers that are supported are ``Accept-Encoding``, +# ``Host``, and ``User-Agent``. +SKIP_HEADER = "@@@SKIP_HEADER@@@" +SKIPPABLE_HEADERS = frozenset(["accept-encoding", "host", "user-agent"]) + +ACCEPT_ENCODING = "gzip,deflate" +try: + try: + import brotlicffi as _unused_module_brotli # type: ignore[import-not-found] # noqa: F401 + except ImportError: + import brotli as _unused_module_brotli # type: ignore[import-not-found] # noqa: F401 +except ImportError: + pass +else: + ACCEPT_ENCODING += ",br" +try: + import zstandard as _unused_module_zstd # noqa: F401 +except ImportError: + pass +else: + ACCEPT_ENCODING += ",zstd" + + +class _TYPE_FAILEDTELL(Enum): + token = 0 + + +_FAILEDTELL: Final[_TYPE_FAILEDTELL] = _TYPE_FAILEDTELL.token + +_TYPE_BODY_POSITION = typing.Union[int, _TYPE_FAILEDTELL] + +# When sending a request with these methods we aren't expecting +# a body so don't need to set an explicit 'Content-Length: 0' +# The reason we do this in the negative instead of tracking methods +# which 'should' have a body is because unknown methods should be +# treated as if they were 'POST' which *does* expect a body. +_METHODS_NOT_EXPECTING_BODY = {"GET", "HEAD", "DELETE", "TRACE", "OPTIONS", "CONNECT"} + + +def make_headers( + keep_alive: bool | None = None, + accept_encoding: bool | list[str] | str | None = None, + user_agent: str | None = None, + basic_auth: str | None = None, + proxy_basic_auth: str | None = None, + disable_cache: bool | None = None, +) -> dict[str, str]: + """ + Shortcuts for generating request headers. + + :param keep_alive: + If ``True``, adds 'connection: keep-alive' header. + + :param accept_encoding: + Can be a boolean, list, or string. + ``True`` translates to 'gzip,deflate'. If the dependencies for + Brotli (either the ``brotli`` or ``brotlicffi`` package) and/or Zstandard + (the ``zstandard`` package) algorithms are installed, then their encodings are + included in the string ('br' and 'zstd', respectively). + List will get joined by comma. + String will be used as provided. + + :param user_agent: + String representing the user-agent you want, such as + "python-urllib3/0.6" + + :param basic_auth: + Colon-separated username:password string for 'authorization: basic ...' + auth header. + + :param proxy_basic_auth: + Colon-separated username:password string for 'proxy-authorization: basic ...' + auth header. + + :param disable_cache: + If ``True``, adds 'cache-control: no-cache' header. + + Example: + + .. code-block:: python + + import urllib3 + + print(urllib3.util.make_headers(keep_alive=True, user_agent="Batman/1.0")) + # {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + print(urllib3.util.make_headers(accept_encoding=True)) + # {'accept-encoding': 'gzip,deflate'} + """ + headers: dict[str, str] = {} + if accept_encoding: + if isinstance(accept_encoding, str): + pass + elif isinstance(accept_encoding, list): + accept_encoding = ",".join(accept_encoding) + else: + accept_encoding = ACCEPT_ENCODING + headers["accept-encoding"] = accept_encoding + + if user_agent: + headers["user-agent"] = user_agent + + if keep_alive: + headers["connection"] = "keep-alive" + + if basic_auth: + headers["authorization"] = ( + f"Basic {b64encode(basic_auth.encode('latin-1')).decode()}" + ) + + if proxy_basic_auth: + headers["proxy-authorization"] = ( + f"Basic {b64encode(proxy_basic_auth.encode('latin-1')).decode()}" + ) + + if disable_cache: + headers["cache-control"] = "no-cache" + + return headers + + +def set_file_position( + body: typing.Any, pos: _TYPE_BODY_POSITION | None +) -> _TYPE_BODY_POSITION | None: + """ + If a position is provided, move file to that point. + Otherwise, we'll attempt to record a position for future use. + """ + if pos is not None: + rewind_body(body, pos) + elif getattr(body, "tell", None) is not None: + try: + pos = body.tell() + except OSError: + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body. + pos = _FAILEDTELL + + return pos + + +def rewind_body(body: typing.IO[typing.AnyStr], body_pos: _TYPE_BODY_POSITION) -> None: + """ + Attempt to rewind body to a certain position. + Primarily used for request redirects and retries. + + :param body: + File-like object that supports seek. + + :param int pos: + Position to seek to in file. + """ + body_seek = getattr(body, "seek", None) + if body_seek is not None and isinstance(body_pos, int): + try: + body_seek(body_pos) + except OSError as e: + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect/retry." + ) from e + elif body_pos is _FAILEDTELL: + raise UnrewindableBodyError( + "Unable to record file position for rewinding " + "request body during a redirect/retry." + ) + else: + raise ValueError( + f"body_pos must be of type integer, instead it was {type(body_pos)}." + ) + + +class ChunksAndContentLength(typing.NamedTuple): + chunks: typing.Iterable[bytes] | None + content_length: int | None + + +def body_to_chunks( + body: typing.Any | None, method: str, blocksize: int +) -> ChunksAndContentLength: + """Takes the HTTP request method, body, and blocksize and + transforms them into an iterable of chunks to pass to + socket.sendall() and an optional 'Content-Length' header. + + A 'Content-Length' of 'None' indicates the length of the body + can't be determined so should use 'Transfer-Encoding: chunked' + for framing instead. + """ + + chunks: typing.Iterable[bytes] | None + content_length: int | None + + # No body, we need to make a recommendation on 'Content-Length' + # based on whether that request method is expected to have + # a body or not. + if body is None: + chunks = None + if method.upper() not in _METHODS_NOT_EXPECTING_BODY: + content_length = 0 + else: + content_length = None + + # Bytes or strings become bytes + elif isinstance(body, (str, bytes)): + chunks = (to_bytes(body),) + content_length = len(chunks[0]) + + # File-like object, TODO: use seek() and tell() for length? + elif hasattr(body, "read"): + + def chunk_readable() -> typing.Iterable[bytes]: + nonlocal body, blocksize + encode = isinstance(body, io.TextIOBase) + while True: + datablock = body.read(blocksize) + if not datablock: + break + if encode: + datablock = datablock.encode("utf-8") + yield datablock + + chunks = chunk_readable() + content_length = None + + # Otherwise we need to start checking via duck-typing. + else: + try: + # Check if the body implements the buffer API. + mv = memoryview(body) + except TypeError: + try: + # Check if the body is an iterable + chunks = iter(body) + content_length = None + except TypeError: + raise TypeError( + f"'body' must be a bytes-like object, file-like " + f"object, or iterable. Instead was {body!r}" + ) from None + else: + # Since it implements the buffer API can be passed directly to socket.sendall() + chunks = (body,) + content_length = mv.nbytes + + return ChunksAndContentLength(chunks=chunks, content_length=content_length) diff --git a/aws/lambda_demo/urllib3/util/response.py b/aws/lambda_demo/urllib3/util/response.py new file mode 100644 index 000000000..0f4578696 --- /dev/null +++ b/aws/lambda_demo/urllib3/util/response.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import http.client as httplib +from email.errors import MultipartInvariantViolationDefect, StartBoundaryNotFoundDefect + +from ..exceptions import HeaderParsingError + + +def is_fp_closed(obj: object) -> bool: + """ + Checks whether a given file-like object is closed. + + :param obj: + The file-like object to check. + """ + + try: + # Check `isclosed()` first, in case Python3 doesn't set `closed`. + # GH Issue #928 + return obj.isclosed() # type: ignore[no-any-return, attr-defined] + except AttributeError: + pass + + try: + # Check via the official file-like-object way. + return obj.closed # type: ignore[no-any-return, attr-defined] + except AttributeError: + pass + + try: + # Check if the object is a container for another file-like object that + # gets released on exhaustion (e.g. HTTPResponse). + return obj.fp is None # type: ignore[attr-defined] + except AttributeError: + pass + + raise ValueError("Unable to determine whether fp is closed.") + + +def assert_header_parsing(headers: httplib.HTTPMessage) -> None: + """ + Asserts whether all headers have been successfully parsed. + Extracts encountered errors from the result of parsing headers. + + Only works on Python 3. + + :param http.client.HTTPMessage headers: Headers to verify. + + :raises urllib3.exceptions.HeaderParsingError: + If parsing errors are found. + """ + + # This will fail silently if we pass in the wrong kind of parameter. + # To make debugging easier add an explicit check. + if not isinstance(headers, httplib.HTTPMessage): + raise TypeError(f"expected httplib.Message, got {type(headers)}.") + + unparsed_data = None + + # get_payload is actually email.message.Message.get_payload; + # we're only interested in the result if it's not a multipart message + if not headers.is_multipart(): + payload = headers.get_payload() + + if isinstance(payload, (bytes, str)): + unparsed_data = payload + + # httplib is assuming a response body is available + # when parsing headers even when httplib only sends + # header data to parse_headers() This results in + # defects on multipart responses in particular. + # See: https://github.com/urllib3/urllib3/issues/800 + + # So we ignore the following defects: + # - StartBoundaryNotFoundDefect: + # The claimed start boundary was never found. + # - MultipartInvariantViolationDefect: + # A message claimed to be a multipart but no subparts were found. + defects = [ + defect + for defect in headers.defects + if not isinstance( + defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect) + ) + ] + + if defects or unparsed_data: + raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) + + +def is_response_to_head(response: httplib.HTTPResponse) -> bool: + """ + Checks whether the request of a response has been a HEAD-request. + + :param http.client.HTTPResponse response: + Response to check if the originating request + used 'HEAD' as a method. + """ + # FIXME: Can we do this somehow without accessing private httplib _method? + method_str = response._method # type: str # type: ignore[attr-defined] + return method_str.upper() == "HEAD" diff --git a/aws/lambda_demo/urllib3/util/retry.py b/aws/lambda_demo/urllib3/util/retry.py new file mode 100644 index 000000000..0456cceba --- /dev/null +++ b/aws/lambda_demo/urllib3/util/retry.py @@ -0,0 +1,533 @@ +from __future__ import annotations + +import email +import logging +import random +import re +import time +import typing +from itertools import takewhile +from types import TracebackType + +from ..exceptions import ( + ConnectTimeoutError, + InvalidHeader, + MaxRetryError, + ProtocolError, + ProxyError, + ReadTimeoutError, + ResponseError, +) +from .util import reraise + +if typing.TYPE_CHECKING: + from typing_extensions import Self + + from ..connectionpool import ConnectionPool + from ..response import BaseHTTPResponse + +log = logging.getLogger(__name__) + + +# Data structure for representing the metadata of requests that result in a retry. +class RequestHistory(typing.NamedTuple): + method: str | None + url: str | None + error: Exception | None + status: int | None + redirect_location: str | None + + +class Retry: + """Retry configuration. + + Each retry attempt will create a new Retry object with updated values, so + they can be safely reused. + + Retries can be defined as a default for a pool: + + .. code-block:: python + + retries = Retry(connect=5, read=2, redirect=5) + http = PoolManager(retries=retries) + response = http.request("GET", "https://example.com/") + + Or per-request (which overrides the default for the pool): + + .. code-block:: python + + response = http.request("GET", "https://example.com/", retries=Retry(10)) + + Retries can be disabled by passing ``False``: + + .. code-block:: python + + response = http.request("GET", "https://example.com/", retries=False) + + Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless + retries are disabled, in which case the causing exception will be raised. + + :param int total: + Total number of retries to allow. Takes precedence over other counts. + + Set to ``None`` to remove this constraint and fall back on other + counts. + + Set to ``0`` to fail on the first retry. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int connect: + How many connection-related errors to retry on. + + These are errors raised before the request is sent to the remote server, + which we assume has not triggered the server to process the request. + + Set to ``0`` to fail on the first retry of this type. + + :param int read: + How many times to retry on read errors. + + These errors are raised after the request was sent to the server, so the + request may have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + :param int redirect: + How many redirects to perform. Limit this to avoid infinite redirect + loops. + + A redirect is a HTTP response with a status code 301, 302, 303, 307 or + 308. + + Set to ``0`` to fail on the first retry of this type. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int status: + How many times to retry on bad status codes. + + These are retries made on responses, where status code matches + ``status_forcelist``. + + Set to ``0`` to fail on the first retry of this type. + + :param int other: + How many times to retry on other errors. + + Other errors are errors that are not connect, read, redirect or status errors. + These errors might be raised after the request was sent to the server, so the + request might have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + If ``total`` is not set, it's a good idea to set this to 0 to account + for unexpected edge cases and avoid infinite retry loops. + + :param Collection allowed_methods: + Set of uppercased HTTP method verbs that we should retry on. + + By default, we only retry on methods which are considered to be + idempotent (multiple requests with the same parameters end with the + same state). See :attr:`Retry.DEFAULT_ALLOWED_METHODS`. + + Set to a ``None`` value to retry on any verb. + + :param Collection status_forcelist: + A set of integer HTTP status codes that we should force a retry on. + A retry is initiated if the request method is in ``allowed_methods`` + and the response status code is in ``status_forcelist``. + + By default, this is disabled with ``None``. + + :param float backoff_factor: + A backoff factor to apply between attempts after the second try + (most errors are resolved immediately by a second try without a + delay). urllib3 will sleep for:: + + {backoff factor} * (2 ** ({number of previous retries})) + + seconds. If `backoff_jitter` is non-zero, this sleep is extended by:: + + random.uniform(0, {backoff jitter}) + + seconds. For example, if the backoff_factor is 0.1, then :func:`Retry.sleep` will + sleep for [0.0s, 0.2s, 0.4s, 0.8s, ...] between retries. No backoff will ever + be longer than `backoff_max`. + + By default, backoff is disabled (factor set to 0). + + :param bool raise_on_redirect: Whether, if the number of redirects is + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. + + :param bool raise_on_status: Similar meaning to ``raise_on_redirect``: + whether we should raise an exception, or return a response, + if status falls in ``status_forcelist`` range and retries have + been exhausted. + + :param tuple history: The history of the request encountered during + each call to :meth:`~Retry.increment`. The list is in the order + the requests occurred. Each list item is of class :class:`RequestHistory`. + + :param bool respect_retry_after_header: + Whether to respect Retry-After header on status codes defined as + :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not. + + :param Collection remove_headers_on_redirect: + Sequence of headers to remove from the request when a response + indicating a redirect is returned before firing off the redirected + request. + """ + + #: Default methods to be used for ``allowed_methods`` + DEFAULT_ALLOWED_METHODS = frozenset( + ["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"] + ) + + #: Default status codes to be used for ``status_forcelist`` + RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) + + #: Default headers to be used for ``remove_headers_on_redirect`` + DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset( + ["Cookie", "Authorization", "Proxy-Authorization"] + ) + + #: Default maximum backoff time. + DEFAULT_BACKOFF_MAX = 120 + + # Backward compatibility; assigned outside of the class. + DEFAULT: typing.ClassVar[Retry] + + def __init__( + self, + total: bool | int | None = 10, + connect: int | None = None, + read: int | None = None, + redirect: bool | int | None = None, + status: int | None = None, + other: int | None = None, + allowed_methods: typing.Collection[str] | None = DEFAULT_ALLOWED_METHODS, + status_forcelist: typing.Collection[int] | None = None, + backoff_factor: float = 0, + backoff_max: float = DEFAULT_BACKOFF_MAX, + raise_on_redirect: bool = True, + raise_on_status: bool = True, + history: tuple[RequestHistory, ...] | None = None, + respect_retry_after_header: bool = True, + remove_headers_on_redirect: typing.Collection[ + str + ] = DEFAULT_REMOVE_HEADERS_ON_REDIRECT, + backoff_jitter: float = 0.0, + ) -> None: + self.total = total + self.connect = connect + self.read = read + self.status = status + self.other = other + + if redirect is False or total is False: + redirect = 0 + raise_on_redirect = False + + self.redirect = redirect + self.status_forcelist = status_forcelist or set() + self.allowed_methods = allowed_methods + self.backoff_factor = backoff_factor + self.backoff_max = backoff_max + self.raise_on_redirect = raise_on_redirect + self.raise_on_status = raise_on_status + self.history = history or () + self.respect_retry_after_header = respect_retry_after_header + self.remove_headers_on_redirect = frozenset( + h.lower() for h in remove_headers_on_redirect + ) + self.backoff_jitter = backoff_jitter + + def new(self, **kw: typing.Any) -> Self: + params = dict( + total=self.total, + connect=self.connect, + read=self.read, + redirect=self.redirect, + status=self.status, + other=self.other, + allowed_methods=self.allowed_methods, + status_forcelist=self.status_forcelist, + backoff_factor=self.backoff_factor, + backoff_max=self.backoff_max, + raise_on_redirect=self.raise_on_redirect, + raise_on_status=self.raise_on_status, + history=self.history, + remove_headers_on_redirect=self.remove_headers_on_redirect, + respect_retry_after_header=self.respect_retry_after_header, + backoff_jitter=self.backoff_jitter, + ) + + params.update(kw) + return type(self)(**params) # type: ignore[arg-type] + + @classmethod + def from_int( + cls, + retries: Retry | bool | int | None, + redirect: bool | int | None = True, + default: Retry | bool | int | None = None, + ) -> Retry: + """Backwards-compatibility for the old retries format.""" + if retries is None: + retries = default if default is not None else cls.DEFAULT + + if isinstance(retries, Retry): + return retries + + redirect = bool(redirect) and None + new_retries = cls(retries, redirect=redirect) + log.debug("Converted retries value: %r -> %r", retries, new_retries) + return new_retries + + def get_backoff_time(self) -> float: + """Formula for computing the current backoff + + :rtype: float + """ + # We want to consider only the last consecutive errors sequence (Ignore redirects). + consecutive_errors_len = len( + list( + takewhile(lambda x: x.redirect_location is None, reversed(self.history)) + ) + ) + if consecutive_errors_len <= 1: + return 0 + + backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1)) + if self.backoff_jitter != 0.0: + backoff_value += random.random() * self.backoff_jitter + return float(max(0, min(self.backoff_max, backoff_value))) + + def parse_retry_after(self, retry_after: str) -> float: + seconds: float + # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4 + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = int(retry_after) + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + raise InvalidHeader(f"Invalid Retry-After header: {retry_after}") + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + seconds = max(seconds, 0) + + return seconds + + def get_retry_after(self, response: BaseHTTPResponse) -> float | None: + """Get the value of Retry-After in seconds.""" + + retry_after = response.headers.get("Retry-After") + + if retry_after is None: + return None + + return self.parse_retry_after(retry_after) + + def sleep_for_retry(self, response: BaseHTTPResponse) -> bool: + retry_after = self.get_retry_after(response) + if retry_after: + time.sleep(retry_after) + return True + + return False + + def _sleep_backoff(self) -> None: + backoff = self.get_backoff_time() + if backoff <= 0: + return + time.sleep(backoff) + + def sleep(self, response: BaseHTTPResponse | None = None) -> None: + """Sleep between retry attempts. + + This method will respect a server's ``Retry-After`` response header + and sleep the duration of the time requested. If that is not present, it + will use an exponential backoff. By default, the backoff factor is 0 and + this method will return immediately. + """ + + if self.respect_retry_after_header and response: + slept = self.sleep_for_retry(response) + if slept: + return + + self._sleep_backoff() + + def _is_connection_error(self, err: Exception) -> bool: + """Errors when we're fairly sure that the server did not receive the + request, so it should be safe to retry. + """ + if isinstance(err, ProxyError): + err = err.original_error + return isinstance(err, ConnectTimeoutError) + + def _is_read_error(self, err: Exception) -> bool: + """Errors that occur after the request has been started, so we should + assume that the server began processing it. + """ + return isinstance(err, (ReadTimeoutError, ProtocolError)) + + def _is_method_retryable(self, method: str) -> bool: + """Checks if a given HTTP method should be retried upon, depending if + it is included in the allowed_methods + """ + if self.allowed_methods and method.upper() not in self.allowed_methods: + return False + return True + + def is_retry( + self, method: str, status_code: int, has_retry_after: bool = False + ) -> bool: + """Is this method/status code retryable? (Based on allowlists and control + variables such as the number of total retries to allow, whether to + respect the Retry-After header, whether this header is present, and + whether the returned status code is on the list of status codes to + be retried upon on the presence of the aforementioned header) + """ + if not self._is_method_retryable(method): + return False + + if self.status_forcelist and status_code in self.status_forcelist: + return True + + return bool( + self.total + and self.respect_retry_after_header + and has_retry_after + and (status_code in self.RETRY_AFTER_STATUS_CODES) + ) + + def is_exhausted(self) -> bool: + """Are we out of retries?""" + retry_counts = [ + x + for x in ( + self.total, + self.connect, + self.read, + self.redirect, + self.status, + self.other, + ) + if x + ] + if not retry_counts: + return False + + return min(retry_counts) < 0 + + def increment( + self, + method: str | None = None, + url: str | None = None, + response: BaseHTTPResponse | None = None, + error: Exception | None = None, + _pool: ConnectionPool | None = None, + _stacktrace: TracebackType | None = None, + ) -> Self: + """Return a new Retry object with incremented retry counters. + + :param response: A response object, or None, if the server did not + return a response. + :type response: :class:`~urllib3.response.BaseHTTPResponse` + :param Exception error: An error encountered during the request, or + None if the response was received successfully. + + :return: A new ``Retry`` object. + """ + if self.total is False and error: + # Disabled, indicate to re-raise the error. + raise reraise(type(error), error, _stacktrace) + + total = self.total + if total is not None: + total -= 1 + + connect = self.connect + read = self.read + redirect = self.redirect + status_count = self.status + other = self.other + cause = "unknown" + status = None + redirect_location = None + + if error and self._is_connection_error(error): + # Connect retry? + if connect is False: + raise reraise(type(error), error, _stacktrace) + elif connect is not None: + connect -= 1 + + elif error and self._is_read_error(error): + # Read retry? + if read is False or method is None or not self._is_method_retryable(method): + raise reraise(type(error), error, _stacktrace) + elif read is not None: + read -= 1 + + elif error: + # Other retry? + if other is not None: + other -= 1 + + elif response and response.get_redirect_location(): + # Redirect retry? + if redirect is not None: + redirect -= 1 + cause = "too many redirects" + response_redirect_location = response.get_redirect_location() + if response_redirect_location: + redirect_location = response_redirect_location + status = response.status + + else: + # Incrementing because of a server error like a 500 in + # status_forcelist and the given method is in the allowed_methods + cause = ResponseError.GENERIC_ERROR + if response and response.status: + if status_count is not None: + status_count -= 1 + cause = ResponseError.SPECIFIC_ERROR.format(status_code=response.status) + status = response.status + + history = self.history + ( + RequestHistory(method, url, error, status, redirect_location), + ) + + new_retry = self.new( + total=total, + connect=connect, + read=read, + redirect=redirect, + status=status_count, + other=other, + history=history, + ) + + if new_retry.is_exhausted(): + reason = error or ResponseError(cause) + raise MaxRetryError(_pool, url, reason) from reason # type: ignore[arg-type] + + log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) + + return new_retry + + def __repr__(self) -> str: + return ( + f"{type(self).__name__}(total={self.total}, connect={self.connect}, " + f"read={self.read}, redirect={self.redirect}, status={self.status})" + ) + + +# For backwards compatibility (equivalent to pre-v1.9): +Retry.DEFAULT = Retry(3) diff --git a/aws/lambda_demo/urllib3/util/ssl_.py b/aws/lambda_demo/urllib3/util/ssl_.py new file mode 100644 index 000000000..278128ebd --- /dev/null +++ b/aws/lambda_demo/urllib3/util/ssl_.py @@ -0,0 +1,504 @@ +from __future__ import annotations + +import hashlib +import hmac +import os +import socket +import sys +import typing +import warnings +from binascii import unhexlify + +from ..exceptions import ProxySchemeUnsupported, SSLError +from .url import _BRACELESS_IPV6_ADDRZ_RE, _IPV4_RE + +SSLContext = None +SSLTransport = None +HAS_NEVER_CHECK_COMMON_NAME = False +IS_PYOPENSSL = False +ALPN_PROTOCOLS = ["http/1.1"] + +_TYPE_VERSION_INFO = tuple[int, int, int, str, int] + +# Maps the length of a digest to a possible hash function producing this digest +HASHFUNC_MAP = { + length: getattr(hashlib, algorithm, None) + for length, algorithm in ((32, "md5"), (40, "sha1"), (64, "sha256")) +} + + +def _is_bpo_43522_fixed( + implementation_name: str, + version_info: _TYPE_VERSION_INFO, + pypy_version_info: _TYPE_VERSION_INFO | None, +) -> bool: + """Return True for CPython 3.9.3+ or 3.10+ and PyPy 7.3.8+ where + setting SSLContext.hostname_checks_common_name to False works. + + Outside of CPython and PyPy we don't know which implementations work + or not so we conservatively use our hostname matching as we know that works + on all implementations. + + https://github.com/urllib3/urllib3/issues/2192#issuecomment-821832963 + https://foss.heptapod.net/pypy/pypy/-/issues/3539 + """ + if implementation_name == "pypy": + # https://foss.heptapod.net/pypy/pypy/-/issues/3129 + return pypy_version_info >= (7, 3, 8) # type: ignore[operator] + elif implementation_name == "cpython": + major_minor = version_info[:2] + micro = version_info[2] + return (major_minor == (3, 9) and micro >= 3) or major_minor >= (3, 10) + else: # Defensive: + return False + + +def _is_has_never_check_common_name_reliable( + openssl_version: str, + openssl_version_number: int, + implementation_name: str, + version_info: _TYPE_VERSION_INFO, + pypy_version_info: _TYPE_VERSION_INFO | None, +) -> bool: + # As of May 2023, all released versions of LibreSSL fail to reject certificates with + # only common names, see https://github.com/urllib3/urllib3/pull/3024 + is_openssl = openssl_version.startswith("OpenSSL ") + # Before fixing OpenSSL issue #14579, the SSL_new() API was not copying hostflags + # like X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, which tripped up CPython. + # https://github.com/openssl/openssl/issues/14579 + # This was released in OpenSSL 1.1.1l+ (>=0x101010cf) + is_openssl_issue_14579_fixed = openssl_version_number >= 0x101010CF + + return is_openssl and ( + is_openssl_issue_14579_fixed + or _is_bpo_43522_fixed(implementation_name, version_info, pypy_version_info) + ) + + +if typing.TYPE_CHECKING: + from ssl import VerifyMode + from typing import TypedDict + + from .ssltransport import SSLTransport as SSLTransportType + + class _TYPE_PEER_CERT_RET_DICT(TypedDict, total=False): + subjectAltName: tuple[tuple[str, str], ...] + subject: tuple[tuple[tuple[str, str], ...], ...] + serialNumber: str + + +# Mapping from 'ssl.PROTOCOL_TLSX' to 'TLSVersion.X' +_SSL_VERSION_TO_TLS_VERSION: dict[int, int] = {} + +try: # Do we have ssl at all? + import ssl + from ssl import ( # type: ignore[assignment] + CERT_REQUIRED, + HAS_NEVER_CHECK_COMMON_NAME, + OP_NO_COMPRESSION, + OP_NO_TICKET, + OPENSSL_VERSION, + OPENSSL_VERSION_NUMBER, + PROTOCOL_TLS, + PROTOCOL_TLS_CLIENT, + OP_NO_SSLv2, + OP_NO_SSLv3, + SSLContext, + TLSVersion, + ) + + PROTOCOL_SSLv23 = PROTOCOL_TLS + + # Setting SSLContext.hostname_checks_common_name = False didn't work before CPython + # 3.9.3, and 3.10 (but OK on PyPy) or OpenSSL 1.1.1l+ + if HAS_NEVER_CHECK_COMMON_NAME and not _is_has_never_check_common_name_reliable( + OPENSSL_VERSION, + OPENSSL_VERSION_NUMBER, + sys.implementation.name, + sys.version_info, + sys.pypy_version_info if sys.implementation.name == "pypy" else None, # type: ignore[attr-defined] + ): + HAS_NEVER_CHECK_COMMON_NAME = False + + # Need to be careful here in case old TLS versions get + # removed in future 'ssl' module implementations. + for attr in ("TLSv1", "TLSv1_1", "TLSv1_2"): + try: + _SSL_VERSION_TO_TLS_VERSION[getattr(ssl, f"PROTOCOL_{attr}")] = getattr( + TLSVersion, attr + ) + except AttributeError: # Defensive: + continue + + from .ssltransport import SSLTransport # type: ignore[assignment] +except ImportError: + OP_NO_COMPRESSION = 0x20000 # type: ignore[assignment] + OP_NO_TICKET = 0x4000 # type: ignore[assignment] + OP_NO_SSLv2 = 0x1000000 # type: ignore[assignment] + OP_NO_SSLv3 = 0x2000000 # type: ignore[assignment] + PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 # type: ignore[assignment] + PROTOCOL_TLS_CLIENT = 16 # type: ignore[assignment] + + +_TYPE_PEER_CERT_RET = typing.Union["_TYPE_PEER_CERT_RET_DICT", bytes, None] + + +def assert_fingerprint(cert: bytes | None, fingerprint: str) -> None: + """ + Checks if given fingerprint matches the supplied certificate. + + :param cert: + Certificate as bytes object. + :param fingerprint: + Fingerprint as string of hexdigits, can be interspersed by colons. + """ + + if cert is None: + raise SSLError("No certificate for the peer.") + + fingerprint = fingerprint.replace(":", "").lower() + digest_length = len(fingerprint) + if digest_length not in HASHFUNC_MAP: + raise SSLError(f"Fingerprint of invalid length: {fingerprint}") + hashfunc = HASHFUNC_MAP.get(digest_length) + if hashfunc is None: + raise SSLError( + f"Hash function implementation unavailable for fingerprint length: {digest_length}" + ) + + # We need encode() here for py32; works on py2 and p33. + fingerprint_bytes = unhexlify(fingerprint.encode()) + + cert_digest = hashfunc(cert).digest() + + if not hmac.compare_digest(cert_digest, fingerprint_bytes): + raise SSLError( + f'Fingerprints did not match. Expected "{fingerprint}", got "{cert_digest.hex()}"' + ) + + +def resolve_cert_reqs(candidate: None | int | str) -> VerifyMode: + """ + Resolves the argument to a numeric constant, which can be passed to + the wrap_socket function/method from the ssl module. + Defaults to :data:`ssl.CERT_REQUIRED`. + If given a string it is assumed to be the name of the constant in the + :mod:`ssl` module or its abbreviation. + (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. + If it's neither `None` nor a string we assume it is already the numeric + constant which can directly be passed to wrap_socket. + """ + if candidate is None: + return CERT_REQUIRED + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "CERT_" + candidate) + return res # type: ignore[no-any-return] + + return candidate # type: ignore[return-value] + + +def resolve_ssl_version(candidate: None | int | str) -> int: + """ + like resolve_cert_reqs + """ + if candidate is None: + return PROTOCOL_TLS + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "PROTOCOL_" + candidate) + return typing.cast(int, res) + + return candidate + + +def create_urllib3_context( + ssl_version: int | None = None, + cert_reqs: int | None = None, + options: int | None = None, + ciphers: str | None = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, +) -> ssl.SSLContext: + """Creates and configures an :class:`ssl.SSLContext` instance for use with urllib3. + + :param ssl_version: + The desired protocol version to use. This will default to + PROTOCOL_SSLv23 which will negotiate the highest protocol that both + the server and your installation of OpenSSL support. + + This parameter is deprecated instead use 'ssl_minimum_version'. + :param ssl_minimum_version: + The minimum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value. + :param ssl_maximum_version: + The maximum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value. + Not recommended to set to anything other than 'ssl.TLSVersion.MAXIMUM_SUPPORTED' which is the + default value. + :param cert_reqs: + Whether to require the certificate verification. This defaults to + ``ssl.CERT_REQUIRED``. + :param options: + Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, + ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``. + :param ciphers: + Which cipher suites to allow the server to select. Defaults to either system configured + ciphers if OpenSSL 1.1.1+, otherwise uses a secure default set of ciphers. + :returns: + Constructed SSLContext object with specified options + :rtype: SSLContext + """ + if SSLContext is None: + raise TypeError("Can't create an SSLContext object without an ssl module") + + # This means 'ssl_version' was specified as an exact value. + if ssl_version not in (None, PROTOCOL_TLS, PROTOCOL_TLS_CLIENT): + # Disallow setting 'ssl_version' and 'ssl_minimum|maximum_version' + # to avoid conflicts. + if ssl_minimum_version is not None or ssl_maximum_version is not None: + raise ValueError( + "Can't specify both 'ssl_version' and either " + "'ssl_minimum_version' or 'ssl_maximum_version'" + ) + + # 'ssl_version' is deprecated and will be removed in the future. + else: + # Use 'ssl_minimum_version' and 'ssl_maximum_version' instead. + ssl_minimum_version = _SSL_VERSION_TO_TLS_VERSION.get( + ssl_version, TLSVersion.MINIMUM_SUPPORTED + ) + ssl_maximum_version = _SSL_VERSION_TO_TLS_VERSION.get( + ssl_version, TLSVersion.MAXIMUM_SUPPORTED + ) + + # This warning message is pushing users to use 'ssl_minimum_version' + # instead of both min/max. Best practice is to only set the minimum version and + # keep the maximum version to be it's default value: 'TLSVersion.MAXIMUM_SUPPORTED' + warnings.warn( + "'ssl_version' option is deprecated and will be " + "removed in urllib3 v2.1.0. Instead use 'ssl_minimum_version'", + category=DeprecationWarning, + stacklevel=2, + ) + + # PROTOCOL_TLS is deprecated in Python 3.10 so we always use PROTOCOL_TLS_CLIENT + context = SSLContext(PROTOCOL_TLS_CLIENT) + + if ssl_minimum_version is not None: + context.minimum_version = ssl_minimum_version + else: # Python <3.10 defaults to 'MINIMUM_SUPPORTED' so explicitly set TLSv1.2 here + context.minimum_version = TLSVersion.TLSv1_2 + + if ssl_maximum_version is not None: + context.maximum_version = ssl_maximum_version + + # Unless we're given ciphers defer to either system ciphers in + # the case of OpenSSL 1.1.1+ or use our own secure default ciphers. + if ciphers: + context.set_ciphers(ciphers) + + # Setting the default here, as we may have no ssl module on import + cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs + + if options is None: + options = 0 + # SSLv2 is easily broken and is considered harmful and dangerous + options |= OP_NO_SSLv2 + # SSLv3 has several problems and is now dangerous + options |= OP_NO_SSLv3 + # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ + # (issue #309) + options |= OP_NO_COMPRESSION + # TLSv1.2 only. Unless set explicitly, do not request tickets. + # This may save some bandwidth on wire, and although the ticket is encrypted, + # there is a risk associated with it being on wire, + # if the server is not rotating its ticketing keys properly. + options |= OP_NO_TICKET + + context.options |= options + + # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is + # necessary for conditional client cert authentication with TLS 1.3. + # The attribute is None for OpenSSL <= 1.1.0 or does not exist when using + # an SSLContext created by pyOpenSSL. + if getattr(context, "post_handshake_auth", None) is not None: + context.post_handshake_auth = True + + # The order of the below lines setting verify_mode and check_hostname + # matter due to safe-guards SSLContext has to prevent an SSLContext with + # check_hostname=True, verify_mode=NONE/OPTIONAL. + # We always set 'check_hostname=False' for pyOpenSSL so we rely on our own + # 'ssl.match_hostname()' implementation. + if cert_reqs == ssl.CERT_REQUIRED and not IS_PYOPENSSL: + context.verify_mode = cert_reqs + context.check_hostname = True + else: + context.check_hostname = False + context.verify_mode = cert_reqs + + try: + context.hostname_checks_common_name = False + except AttributeError: # Defensive: for CPython < 3.9.3; for PyPy < 7.3.8 + pass + + sslkeylogfile = os.environ.get("SSLKEYLOGFILE") + if sslkeylogfile: + context.keylog_filename = sslkeylogfile + + return context + + +@typing.overload +def ssl_wrap_socket( + sock: socket.socket, + keyfile: str | None = ..., + certfile: str | None = ..., + cert_reqs: int | None = ..., + ca_certs: str | None = ..., + server_hostname: str | None = ..., + ssl_version: int | None = ..., + ciphers: str | None = ..., + ssl_context: ssl.SSLContext | None = ..., + ca_cert_dir: str | None = ..., + key_password: str | None = ..., + ca_cert_data: None | str | bytes = ..., + tls_in_tls: typing.Literal[False] = ..., +) -> ssl.SSLSocket: ... + + +@typing.overload +def ssl_wrap_socket( + sock: socket.socket, + keyfile: str | None = ..., + certfile: str | None = ..., + cert_reqs: int | None = ..., + ca_certs: str | None = ..., + server_hostname: str | None = ..., + ssl_version: int | None = ..., + ciphers: str | None = ..., + ssl_context: ssl.SSLContext | None = ..., + ca_cert_dir: str | None = ..., + key_password: str | None = ..., + ca_cert_data: None | str | bytes = ..., + tls_in_tls: bool = ..., +) -> ssl.SSLSocket | SSLTransportType: ... + + +def ssl_wrap_socket( + sock: socket.socket, + keyfile: str | None = None, + certfile: str | None = None, + cert_reqs: int | None = None, + ca_certs: str | None = None, + server_hostname: str | None = None, + ssl_version: int | None = None, + ciphers: str | None = None, + ssl_context: ssl.SSLContext | None = None, + ca_cert_dir: str | None = None, + key_password: str | None = None, + ca_cert_data: None | str | bytes = None, + tls_in_tls: bool = False, +) -> ssl.SSLSocket | SSLTransportType: + """ + All arguments except for server_hostname, ssl_context, tls_in_tls, ca_cert_data and + ca_cert_dir have the same meaning as they do when using + :func:`ssl.create_default_context`, :meth:`ssl.SSLContext.load_cert_chain`, + :meth:`ssl.SSLContext.set_ciphers` and :meth:`ssl.SSLContext.wrap_socket`. + + :param server_hostname: + When SNI is supported, the expected hostname of the certificate + :param ssl_context: + A pre-made :class:`SSLContext` object. If none is provided, one will + be created using :func:`create_urllib3_context`. + :param ciphers: + A string of ciphers we wish the client to support. + :param ca_cert_dir: + A directory containing CA certificates in multiple separate files, as + supported by OpenSSL's -CApath flag or the capath argument to + SSLContext.load_verify_locations(). + :param key_password: + Optional password if the keyfile is encrypted. + :param ca_cert_data: + Optional string containing CA certificates in PEM format suitable for + passing as the cadata parameter to SSLContext.load_verify_locations() + :param tls_in_tls: + Use SSLTransport to wrap the existing socket. + """ + context = ssl_context + if context is None: + # Note: This branch of code and all the variables in it are only used in tests. + # We should consider deprecating and removing this code. + context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers) + + if ca_certs or ca_cert_dir or ca_cert_data: + try: + context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data) + except OSError as e: + raise SSLError(e) from e + + elif ssl_context is None and hasattr(context, "load_default_certs"): + # try to load OS default certs; works well on Windows. + context.load_default_certs() + + # Attempt to detect if we get the goofy behavior of the + # keyfile being encrypted and OpenSSL asking for the + # passphrase via the terminal and instead error out. + if keyfile and key_password is None and _is_key_file_encrypted(keyfile): + raise SSLError("Client private key is encrypted, password is required") + + if certfile: + if key_password is None: + context.load_cert_chain(certfile, keyfile) + else: + context.load_cert_chain(certfile, keyfile, key_password) + + context.set_alpn_protocols(ALPN_PROTOCOLS) + + ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname) + return ssl_sock + + +def is_ipaddress(hostname: str | bytes) -> bool: + """Detects whether the hostname given is an IPv4 or IPv6 address. + Also detects IPv6 addresses with Zone IDs. + + :param str hostname: Hostname to examine. + :return: True if the hostname is an IP address, False otherwise. + """ + if isinstance(hostname, bytes): + # IDN A-label bytes are ASCII compatible. + hostname = hostname.decode("ascii") + return bool(_IPV4_RE.match(hostname) or _BRACELESS_IPV6_ADDRZ_RE.match(hostname)) + + +def _is_key_file_encrypted(key_file: str) -> bool: + """Detects if a key file is encrypted or not.""" + with open(key_file) as f: + for line in f: + # Look for Proc-Type: 4,ENCRYPTED + if "ENCRYPTED" in line: + return True + + return False + + +def _ssl_wrap_socket_impl( + sock: socket.socket, + ssl_context: ssl.SSLContext, + tls_in_tls: bool, + server_hostname: str | None = None, +) -> ssl.SSLSocket | SSLTransportType: + if tls_in_tls: + if not SSLTransport: + # Import error, ssl is not available. + raise ProxySchemeUnsupported( + "TLS in TLS requires support for the 'ssl' module" + ) + + SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context) + return SSLTransport(sock, ssl_context, server_hostname) + + return ssl_context.wrap_socket(sock, server_hostname=server_hostname) diff --git a/aws/lambda_demo/urllib3/util/ssl_match_hostname.py b/aws/lambda_demo/urllib3/util/ssl_match_hostname.py new file mode 100644 index 000000000..453cfd420 --- /dev/null +++ b/aws/lambda_demo/urllib3/util/ssl_match_hostname.py @@ -0,0 +1,159 @@ +"""The match_hostname() function from Python 3.5, essential when using SSL.""" + +# Note: This file is under the PSF license as the code comes from the python +# stdlib. http://docs.python.org/3/license.html +# It is modified to remove commonName support. + +from __future__ import annotations + +import ipaddress +import re +import typing +from ipaddress import IPv4Address, IPv6Address + +if typing.TYPE_CHECKING: + from .ssl_ import _TYPE_PEER_CERT_RET_DICT + +__version__ = "3.5.0.1" + + +class CertificateError(ValueError): + pass + + +def _dnsname_match( + dn: typing.Any, hostname: str, max_wildcards: int = 1 +) -> typing.Match[str] | None | bool: + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + # Ported from python3-syntax: + # leftmost, *remainder = dn.split(r'.') + parts = dn.split(r".") + leftmost = parts[0] + remainder = parts[1:] + + wildcards = leftmost.count("*") + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn) + ) + + # speed up common case w/o wildcards + if not wildcards: + return bool(dn.lower() == hostname.lower()) + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == "*": + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append("[^.]+") + elif leftmost.startswith("xn--") or hostname.startswith("xn--"): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r"\*", "[^.]*")) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r"\A" + r"\.".join(pats) + r"\Z", re.IGNORECASE) + return pat.match(hostname) + + +def _ipaddress_match(ipname: str, host_ip: IPv4Address | IPv6Address) -> bool: + """Exact matching of IP addresses. + + RFC 9110 section 4.3.5: "A reference identity of IP-ID contains the decoded + bytes of the IP address. An IP version 4 address is 4 octets, and an IP + version 6 address is 16 octets. [...] A reference identity of type IP-ID + matches if the address is identical to an iPAddress value of the + subjectAltName extension of the certificate." + """ + # OpenSSL may add a trailing newline to a subjectAltName's IP address + # Divergence from upstream: ipaddress can't handle byte str + ip = ipaddress.ip_address(ipname.rstrip()) + return bool(ip.packed == host_ip.packed) + + +def match_hostname( + cert: _TYPE_PEER_CERT_RET_DICT | None, + hostname: str, + hostname_checks_common_name: bool = False, +) -> None: + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError( + "empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED" + ) + try: + # Divergence from upstream: ipaddress can't handle byte str + # + # The ipaddress module shipped with Python < 3.9 does not support + # scoped IPv6 addresses so we unconditionally strip the Zone IDs for + # now. Once we drop support for Python 3.9 we can remove this branch. + if "%" in hostname: + host_ip = ipaddress.ip_address(hostname[: hostname.rfind("%")]) + else: + host_ip = ipaddress.ip_address(hostname) + + except ValueError: + # Not an IP address (common case) + host_ip = None + dnsnames = [] + san: tuple[tuple[str, str], ...] = cert.get("subjectAltName", ()) + key: str + value: str + for key, value in san: + if key == "DNS": + if host_ip is None and _dnsname_match(value, hostname): + return + dnsnames.append(value) + elif key == "IP Address": + if host_ip is not None and _ipaddress_match(value, host_ip): + return + dnsnames.append(value) + + # We only check 'commonName' if it's enabled and we're not verifying + # an IP address. IP addresses aren't valid within 'commonName'. + if hostname_checks_common_name and host_ip is None and not dnsnames: + for sub in cert.get("subject", ()): + for key, value in sub: + if key == "commonName": + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + + if len(dnsnames) > 1: + raise CertificateError( + "hostname %r " + "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames))) + ) + elif len(dnsnames) == 1: + raise CertificateError(f"hostname {hostname!r} doesn't match {dnsnames[0]!r}") + else: + raise CertificateError("no appropriate subjectAltName fields were found") diff --git a/aws/lambda_demo/urllib3/util/ssltransport.py b/aws/lambda_demo/urllib3/util/ssltransport.py new file mode 100644 index 000000000..6d59bc3bc --- /dev/null +++ b/aws/lambda_demo/urllib3/util/ssltransport.py @@ -0,0 +1,271 @@ +from __future__ import annotations + +import io +import socket +import ssl +import typing + +from ..exceptions import ProxySchemeUnsupported + +if typing.TYPE_CHECKING: + from typing_extensions import Self + + from .ssl_ import _TYPE_PEER_CERT_RET, _TYPE_PEER_CERT_RET_DICT + + +_WriteBuffer = typing.Union[bytearray, memoryview] +_ReturnValue = typing.TypeVar("_ReturnValue") + +SSL_BLOCKSIZE = 16384 + + +class SSLTransport: + """ + The SSLTransport wraps an existing socket and establishes an SSL connection. + + Contrary to Python's implementation of SSLSocket, it allows you to chain + multiple TLS connections together. It's particularly useful if you need to + implement TLS within TLS. + + The class supports most of the socket API operations. + """ + + @staticmethod + def _validate_ssl_context_for_tls_in_tls(ssl_context: ssl.SSLContext) -> None: + """ + Raises a ProxySchemeUnsupported if the provided ssl_context can't be used + for TLS in TLS. + + The only requirement is that the ssl_context provides the 'wrap_bio' + methods. + """ + + if not hasattr(ssl_context, "wrap_bio"): + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "available on non-native SSLContext" + ) + + def __init__( + self, + socket: socket.socket, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + suppress_ragged_eofs: bool = True, + ) -> None: + """ + Create an SSLTransport around socket using the provided ssl_context. + """ + self.incoming = ssl.MemoryBIO() + self.outgoing = ssl.MemoryBIO() + + self.suppress_ragged_eofs = suppress_ragged_eofs + self.socket = socket + + self.sslobj = ssl_context.wrap_bio( + self.incoming, self.outgoing, server_hostname=server_hostname + ) + + # Perform initial handshake. + self._ssl_io_loop(self.sslobj.do_handshake) + + def __enter__(self) -> Self: + return self + + def __exit__(self, *_: typing.Any) -> None: + self.close() + + def fileno(self) -> int: + return self.socket.fileno() + + def read(self, len: int = 1024, buffer: typing.Any | None = None) -> int | bytes: + return self._wrap_ssl_read(len, buffer) + + def recv(self, buflen: int = 1024, flags: int = 0) -> int | bytes: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv") + return self._wrap_ssl_read(buflen) + + def recv_into( + self, + buffer: _WriteBuffer, + nbytes: int | None = None, + flags: int = 0, + ) -> None | int | bytes: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv_into") + if nbytes is None: + nbytes = len(buffer) + return self.read(nbytes, buffer) + + def sendall(self, data: bytes, flags: int = 0) -> None: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to sendall") + count = 0 + with memoryview(data) as view, view.cast("B") as byte_view: + amount = len(byte_view) + while count < amount: + v = self.send(byte_view[count:]) + count += v + + def send(self, data: bytes, flags: int = 0) -> int: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to send") + return self._ssl_io_loop(self.sslobj.write, data) + + def makefile( + self, + mode: str, + buffering: int | None = None, + *, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> typing.BinaryIO | typing.TextIO | socket.SocketIO: + """ + Python's httpclient uses makefile and buffered io when reading HTTP + messages and we need to support it. + + This is unfortunately a copy and paste of socket.py makefile with small + changes to point to the socket directly. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError(f"invalid mode {mode!r} (only r, w, b allowed)") + + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = socket.SocketIO(self, rawmode) # type: ignore[arg-type] + self.socket._io_refs += 1 # type: ignore[attr-defined] + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + buffer: typing.BinaryIO + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) # type: ignore[assignment] + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode # type: ignore[misc] + return text + + def unwrap(self) -> None: + self._ssl_io_loop(self.sslobj.unwrap) + + def close(self) -> None: + self.socket.close() + + @typing.overload + def getpeercert( + self, binary_form: typing.Literal[False] = ... + ) -> _TYPE_PEER_CERT_RET_DICT | None: ... + + @typing.overload + def getpeercert(self, binary_form: typing.Literal[True]) -> bytes | None: ... + + def getpeercert(self, binary_form: bool = False) -> _TYPE_PEER_CERT_RET: + return self.sslobj.getpeercert(binary_form) # type: ignore[return-value] + + def version(self) -> str | None: + return self.sslobj.version() + + def cipher(self) -> tuple[str, str, int] | None: + return self.sslobj.cipher() + + def selected_alpn_protocol(self) -> str | None: + return self.sslobj.selected_alpn_protocol() + + def shared_ciphers(self) -> list[tuple[str, str, int]] | None: + return self.sslobj.shared_ciphers() + + def compression(self) -> str | None: + return self.sslobj.compression() + + def settimeout(self, value: float | None) -> None: + self.socket.settimeout(value) + + def gettimeout(self) -> float | None: + return self.socket.gettimeout() + + def _decref_socketios(self) -> None: + self.socket._decref_socketios() # type: ignore[attr-defined] + + def _wrap_ssl_read(self, len: int, buffer: bytearray | None = None) -> int | bytes: + try: + return self._ssl_io_loop(self.sslobj.read, len, buffer) + except ssl.SSLError as e: + if e.errno == ssl.SSL_ERROR_EOF and self.suppress_ragged_eofs: + return 0 # eof, return 0. + else: + raise + + # func is sslobj.do_handshake or sslobj.unwrap + @typing.overload + def _ssl_io_loop(self, func: typing.Callable[[], None]) -> None: ... + + # func is sslobj.write, arg1 is data + @typing.overload + def _ssl_io_loop(self, func: typing.Callable[[bytes], int], arg1: bytes) -> int: ... + + # func is sslobj.read, arg1 is len, arg2 is buffer + @typing.overload + def _ssl_io_loop( + self, + func: typing.Callable[[int, bytearray | None], bytes], + arg1: int, + arg2: bytearray | None, + ) -> bytes: ... + + def _ssl_io_loop( + self, + func: typing.Callable[..., _ReturnValue], + arg1: None | bytes | int = None, + arg2: bytearray | None = None, + ) -> _ReturnValue: + """Performs an I/O loop between incoming/outgoing and the socket.""" + should_loop = True + ret = None + + while should_loop: + errno = None + try: + if arg1 is None and arg2 is None: + ret = func() + elif arg2 is None: + ret = func(arg1) + else: + ret = func(arg1, arg2) + except ssl.SSLError as e: + if e.errno not in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): + # WANT_READ, and WANT_WRITE are expected, others are not. + raise e + errno = e.errno + + buf = self.outgoing.read() + self.socket.sendall(buf) + + if errno is None: + should_loop = False + elif errno == ssl.SSL_ERROR_WANT_READ: + buf = self.socket.recv(SSL_BLOCKSIZE) + if buf: + self.incoming.write(buf) + else: + self.incoming.write_eof() + return typing.cast(_ReturnValue, ret) diff --git a/aws/lambda_demo/urllib3/util/timeout.py b/aws/lambda_demo/urllib3/util/timeout.py new file mode 100644 index 000000000..4bb1be11d --- /dev/null +++ b/aws/lambda_demo/urllib3/util/timeout.py @@ -0,0 +1,275 @@ +from __future__ import annotations + +import time +import typing +from enum import Enum +from socket import getdefaulttimeout + +from ..exceptions import TimeoutStateError + +if typing.TYPE_CHECKING: + from typing import Final + + +class _TYPE_DEFAULT(Enum): + # This value should never be passed to socket.settimeout() so for safety we use a -1. + # socket.settimout() raises a ValueError for negative values. + token = -1 + + +_DEFAULT_TIMEOUT: Final[_TYPE_DEFAULT] = _TYPE_DEFAULT.token + +_TYPE_TIMEOUT = typing.Optional[typing.Union[float, _TYPE_DEFAULT]] + + +class Timeout: + """Timeout configuration. + + Timeouts can be defined as a default for a pool: + + .. code-block:: python + + import urllib3 + + timeout = urllib3.util.Timeout(connect=2.0, read=7.0) + + http = urllib3.PoolManager(timeout=timeout) + + resp = http.request("GET", "https://example.com/") + + print(resp.status) + + Or per-request (which overrides the default for the pool): + + .. code-block:: python + + response = http.request("GET", "https://example.com/", timeout=Timeout(10)) + + Timeouts can be disabled by setting all the parameters to ``None``: + + .. code-block:: python + + no_timeout = Timeout(connect=None, read=None) + response = http.request("GET", "https://example.com/", timeout=no_timeout) + + + :param total: + This combines the connect and read timeouts into one; the read timeout + will be set to the time leftover from the connect attempt. In the + event that both a connect timeout and a total are specified, or a read + timeout and a total are specified, the shorter timeout will be applied. + + Defaults to None. + + :type total: int, float, or None + + :param connect: + The maximum amount of time (in seconds) to wait for a connection + attempt to a server to succeed. Omitting the parameter will default the + connect timeout to the system default, probably `the global default + timeout in socket.py + `_. + None will set an infinite timeout for connection attempts. + + :type connect: int, float, or None + + :param read: + The maximum amount of time (in seconds) to wait between consecutive + read operations for a response from the server. Omitting the parameter + will default the read timeout to the system default, probably `the + global default timeout in socket.py + `_. + None will set an infinite timeout. + + :type read: int, float, or None + + .. note:: + + Many factors can affect the total amount of time for urllib3 to return + an HTTP response. + + For example, Python's DNS resolver does not obey the timeout specified + on the socket. Other factors that can affect total request time include + high CPU load, high swap, the program running at a low priority level, + or other behaviors. + + In addition, the read and total timeouts only measure the time between + read operations on the socket connecting the client and the server, + not the total amount of time for the request to return a complete + response. For most requests, the timeout is raised because the server + has not sent the first byte in the specified time. This is not always + the case; if a server streams one byte every fifteen seconds, a timeout + of 20 seconds will not trigger, even though the request will take + several minutes to complete. + """ + + #: A sentinel object representing the default timeout value + DEFAULT_TIMEOUT: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT + + def __init__( + self, + total: _TYPE_TIMEOUT = None, + connect: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + read: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + ) -> None: + self._connect = self._validate_timeout(connect, "connect") + self._read = self._validate_timeout(read, "read") + self.total = self._validate_timeout(total, "total") + self._start_connect: float | None = None + + def __repr__(self) -> str: + return f"{type(self).__name__}(connect={self._connect!r}, read={self._read!r}, total={self.total!r})" + + # __str__ provided for backwards compatibility + __str__ = __repr__ + + @staticmethod + def resolve_default_timeout(timeout: _TYPE_TIMEOUT) -> float | None: + return getdefaulttimeout() if timeout is _DEFAULT_TIMEOUT else timeout + + @classmethod + def _validate_timeout(cls, value: _TYPE_TIMEOUT, name: str) -> _TYPE_TIMEOUT: + """Check that a timeout attribute is valid. + + :param value: The timeout value to validate + :param name: The name of the timeout attribute to validate. This is + used to specify in error messages. + :return: The validated and casted version of the given value. + :raises ValueError: If it is a numeric value less than or equal to + zero, or the type is not an integer, float, or None. + """ + if value is None or value is _DEFAULT_TIMEOUT: + return value + + if isinstance(value, bool): + raise ValueError( + "Timeout cannot be a boolean value. It must " + "be an int, float or None." + ) + try: + float(value) + except (TypeError, ValueError): + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) from None + + try: + if value <= 0: + raise ValueError( + "Attempted to set %s timeout to %s, but the " + "timeout cannot be set to a value less " + "than or equal to 0." % (name, value) + ) + except TypeError: + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) from None + + return value + + @classmethod + def from_float(cls, timeout: _TYPE_TIMEOUT) -> Timeout: + """Create a new Timeout from a legacy timeout value. + + The timeout value used by httplib.py sets the same timeout on the + connect(), and recv() socket requests. This creates a :class:`Timeout` + object that sets the individual timeouts to the ``timeout`` value + passed to this function. + + :param timeout: The legacy timeout value. + :type timeout: integer, float, :attr:`urllib3.util.Timeout.DEFAULT_TIMEOUT`, or None + :return: Timeout object + :rtype: :class:`Timeout` + """ + return Timeout(read=timeout, connect=timeout) + + def clone(self) -> Timeout: + """Create a copy of the timeout object + + Timeout properties are stored per-pool but each request needs a fresh + Timeout object to ensure each one has its own start/stop configured. + + :return: a copy of the timeout object + :rtype: :class:`Timeout` + """ + # We can't use copy.deepcopy because that will also create a new object + # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to + # detect the user default. + return Timeout(connect=self._connect, read=self._read, total=self.total) + + def start_connect(self) -> float: + """Start the timeout clock, used during a connect() attempt + + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to start a timer that has been started already. + """ + if self._start_connect is not None: + raise TimeoutStateError("Timeout timer has already been started.") + self._start_connect = time.monotonic() + return self._start_connect + + def get_connect_duration(self) -> float: + """Gets the time elapsed since the call to :meth:`start_connect`. + + :return: Elapsed time in seconds. + :rtype: float + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to get duration for a timer that hasn't been started. + """ + if self._start_connect is None: + raise TimeoutStateError( + "Can't get connect duration for timer that has not started." + ) + return time.monotonic() - self._start_connect + + @property + def connect_timeout(self) -> _TYPE_TIMEOUT: + """Get the value to use when setting a connection timeout. + + This will be a positive float or integer, the value None + (never timeout), or the default system timeout. + + :return: Connect timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + """ + if self.total is None: + return self._connect + + if self._connect is None or self._connect is _DEFAULT_TIMEOUT: + return self.total + + return min(self._connect, self.total) # type: ignore[type-var] + + @property + def read_timeout(self) -> float | None: + """Get the value for the read timeout. + + This assumes some time has elapsed in the connection timeout and + computes the read timeout appropriately. + + If self.total is set, the read timeout is dependent on the amount of + time taken by the connect timeout. If the connection time has not been + established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be + raised. + + :return: Value to use for the read timeout. + :rtype: int, float or None + :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` + has not yet been called on this object. + """ + if ( + self.total is not None + and self.total is not _DEFAULT_TIMEOUT + and self._read is not None + and self._read is not _DEFAULT_TIMEOUT + ): + # In case the connect timeout has not yet been established. + if self._start_connect is None: + return self._read + return max(0, min(self.total - self.get_connect_duration(), self._read)) + elif self.total is not None and self.total is not _DEFAULT_TIMEOUT: + return max(0, self.total - self.get_connect_duration()) + else: + return self.resolve_default_timeout(self._read) diff --git a/aws/lambda_demo/urllib3/util/url.py b/aws/lambda_demo/urllib3/util/url.py new file mode 100644 index 000000000..db057f17b --- /dev/null +++ b/aws/lambda_demo/urllib3/util/url.py @@ -0,0 +1,469 @@ +from __future__ import annotations + +import re +import typing + +from ..exceptions import LocationParseError +from .util import to_str + +# We only want to normalize urls with an HTTP(S) scheme. +# urllib3 infers URLs without a scheme (None) to be http. +_NORMALIZABLE_SCHEMES = ("http", "https", None) + +# Almost all of these patterns were derived from the +# 'rfc3986' module: https://github.com/python-hyper/rfc3986 +_PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}") +_SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)") +_URI_RE = re.compile( + r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?" + r"(?://([^\\/?#]*))?" + r"([^?#]*)" + r"(?:\?([^#]*))?" + r"(?:#(.*))?$", + re.UNICODE | re.DOTALL, +) + +_IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" +_HEX_PAT = "[0-9A-Fa-f]{1,4}" +_LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=_HEX_PAT, ipv4=_IPV4_PAT) +_subs = {"hex": _HEX_PAT, "ls32": _LS32_PAT} +_variations = [ + # 6( h16 ":" ) ls32 + "(?:%(hex)s:){6}%(ls32)s", + # "::" 5( h16 ":" ) ls32 + "::(?:%(hex)s:){5}%(ls32)s", + # [ h16 ] "::" 4( h16 ":" ) ls32 + "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s", + # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s", + # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s", + # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s", + # [ *4( h16 ":" ) h16 ] "::" ls32 + "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s", + # [ *5( h16 ":" ) h16 ] "::" h16 + "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s", + # [ *6( h16 ":" ) h16 ] "::" + "(?:(?:%(hex)s:){0,6}%(hex)s)?::", +] + +_UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._\-~" +_IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")" +_ZONE_ID_PAT = "(?:%25|%)(?:[" + _UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+" +_IPV6_ADDRZ_PAT = r"\[" + _IPV6_PAT + r"(?:" + _ZONE_ID_PAT + r")?\]" +_REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*" +_TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$") + +_IPV4_RE = re.compile("^" + _IPV4_PAT + "$") +_IPV6_RE = re.compile("^" + _IPV6_PAT + "$") +_IPV6_ADDRZ_RE = re.compile("^" + _IPV6_ADDRZ_PAT + "$") +_BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + _IPV6_ADDRZ_PAT[2:-2] + "$") +_ZONE_ID_RE = re.compile("(" + _ZONE_ID_PAT + r")\]$") + +_HOST_PORT_PAT = ("^(%s|%s|%s)(?::0*?(|0|[1-9][0-9]{0,4}))?$") % ( + _REG_NAME_PAT, + _IPV4_PAT, + _IPV6_ADDRZ_PAT, +) +_HOST_PORT_RE = re.compile(_HOST_PORT_PAT, re.UNICODE | re.DOTALL) + +_UNRESERVED_CHARS = set( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~" +) +_SUB_DELIM_CHARS = set("!$&'()*+,;=") +_USERINFO_CHARS = _UNRESERVED_CHARS | _SUB_DELIM_CHARS | {":"} +_PATH_CHARS = _USERINFO_CHARS | {"@", "/"} +_QUERY_CHARS = _FRAGMENT_CHARS = _PATH_CHARS | {"?"} + + +class Url( + typing.NamedTuple( + "Url", + [ + ("scheme", typing.Optional[str]), + ("auth", typing.Optional[str]), + ("host", typing.Optional[str]), + ("port", typing.Optional[int]), + ("path", typing.Optional[str]), + ("query", typing.Optional[str]), + ("fragment", typing.Optional[str]), + ], + ) +): + """ + Data structure for representing an HTTP URL. Used as a return value for + :func:`parse_url`. Both the scheme and host are normalized as they are + both case-insensitive according to RFC 3986. + """ + + def __new__( # type: ignore[no-untyped-def] + cls, + scheme: str | None = None, + auth: str | None = None, + host: str | None = None, + port: int | None = None, + path: str | None = None, + query: str | None = None, + fragment: str | None = None, + ): + if path and not path.startswith("/"): + path = "/" + path + if scheme is not None: + scheme = scheme.lower() + return super().__new__(cls, scheme, auth, host, port, path, query, fragment) + + @property + def hostname(self) -> str | None: + """For backwards-compatibility with urlparse. We're nice like that.""" + return self.host + + @property + def request_uri(self) -> str: + """Absolute path including the query string.""" + uri = self.path or "/" + + if self.query is not None: + uri += "?" + self.query + + return uri + + @property + def authority(self) -> str | None: + """ + Authority component as defined in RFC 3986 3.2. + This includes userinfo (auth), host and port. + + i.e. + userinfo@host:port + """ + userinfo = self.auth + netloc = self.netloc + if netloc is None or userinfo is None: + return netloc + else: + return f"{userinfo}@{netloc}" + + @property + def netloc(self) -> str | None: + """ + Network location including host and port. + + If you need the equivalent of urllib.parse's ``netloc``, + use the ``authority`` property instead. + """ + if self.host is None: + return None + if self.port: + return f"{self.host}:{self.port}" + return self.host + + @property + def url(self) -> str: + """ + Convert self into a url + + This function should more or less round-trip with :func:`.parse_url`. The + returned url may not be exactly the same as the url inputted to + :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls + with a blank port will have : removed). + + Example: + + .. code-block:: python + + import urllib3 + + U = urllib3.util.parse_url("https://google.com/mail/") + + print(U.url) + # "https://google.com/mail/" + + print( urllib3.util.Url("https", "username:password", + "host.com", 80, "/path", "query", "fragment" + ).url + ) + # "https://username:password@host.com:80/path?query#fragment" + """ + scheme, auth, host, port, path, query, fragment = self + url = "" + + # We use "is not None" we want things to happen with empty strings (or 0 port) + if scheme is not None: + url += scheme + "://" + if auth is not None: + url += auth + "@" + if host is not None: + url += host + if port is not None: + url += ":" + str(port) + if path is not None: + url += path + if query is not None: + url += "?" + query + if fragment is not None: + url += "#" + fragment + + return url + + def __str__(self) -> str: + return self.url + + +@typing.overload +def _encode_invalid_chars( + component: str, allowed_chars: typing.Container[str] +) -> str: # Abstract + ... + + +@typing.overload +def _encode_invalid_chars( + component: None, allowed_chars: typing.Container[str] +) -> None: # Abstract + ... + + +def _encode_invalid_chars( + component: str | None, allowed_chars: typing.Container[str] +) -> str | None: + """Percent-encodes a URI component without reapplying + onto an already percent-encoded component. + """ + if component is None: + return component + + component = to_str(component) + + # Normalize existing percent-encoded bytes. + # Try to see if the component we're encoding is already percent-encoded + # so we can skip all '%' characters but still encode all others. + component, percent_encodings = _PERCENT_RE.subn( + lambda match: match.group(0).upper(), component + ) + + uri_bytes = component.encode("utf-8", "surrogatepass") + is_percent_encoded = percent_encodings == uri_bytes.count(b"%") + encoded_component = bytearray() + + for i in range(0, len(uri_bytes)): + # Will return a single character bytestring + byte = uri_bytes[i : i + 1] + byte_ord = ord(byte) + if (is_percent_encoded and byte == b"%") or ( + byte_ord < 128 and byte.decode() in allowed_chars + ): + encoded_component += byte + continue + encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper())) + + return encoded_component.decode() + + +def _remove_path_dot_segments(path: str) -> str: + # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code + segments = path.split("/") # Turn the path into a list of segments + output = [] # Initialize the variable to use to store output + + for segment in segments: + # '.' is the current directory, so ignore it, it is superfluous + if segment == ".": + continue + # Anything other than '..', should be appended to the output + if segment != "..": + output.append(segment) + # In this case segment == '..', if we can, we should pop the last + # element + elif output: + output.pop() + + # If the path starts with '/' and the output is empty or the first string + # is non-empty + if path.startswith("/") and (not output or output[0]): + output.insert(0, "") + + # If the path starts with '/.' or '/..' ensure we add one more empty + # string to add a trailing '/' + if path.endswith(("/.", "/..")): + output.append("") + + return "/".join(output) + + +@typing.overload +def _normalize_host(host: None, scheme: str | None) -> None: ... + + +@typing.overload +def _normalize_host(host: str, scheme: str | None) -> str: ... + + +def _normalize_host(host: str | None, scheme: str | None) -> str | None: + if host: + if scheme in _NORMALIZABLE_SCHEMES: + is_ipv6 = _IPV6_ADDRZ_RE.match(host) + if is_ipv6: + # IPv6 hosts of the form 'a::b%zone' are encoded in a URL as + # such per RFC 6874: 'a::b%25zone'. Unquote the ZoneID + # separator as necessary to return a valid RFC 4007 scoped IP. + match = _ZONE_ID_RE.search(host) + if match: + start, end = match.span(1) + zone_id = host[start:end] + + if zone_id.startswith("%25") and zone_id != "%25": + zone_id = zone_id[3:] + else: + zone_id = zone_id[1:] + zone_id = _encode_invalid_chars(zone_id, _UNRESERVED_CHARS) + return f"{host[:start].lower()}%{zone_id}{host[end:]}" + else: + return host.lower() + elif not _IPV4_RE.match(host): + return to_str( + b".".join([_idna_encode(label) for label in host.split(".")]), + "ascii", + ) + return host + + +def _idna_encode(name: str) -> bytes: + if not name.isascii(): + try: + import idna + except ImportError: + raise LocationParseError( + "Unable to parse URL without the 'idna' module" + ) from None + + try: + return idna.encode(name.lower(), strict=True, std3_rules=True) + except idna.IDNAError: + raise LocationParseError( + f"Name '{name}' is not a valid IDNA label" + ) from None + + return name.lower().encode("ascii") + + +def _encode_target(target: str) -> str: + """Percent-encodes a request target so that there are no invalid characters + + Pre-condition for this function is that 'target' must start with '/'. + If that is the case then _TARGET_RE will always produce a match. + """ + match = _TARGET_RE.match(target) + if not match: # Defensive: + raise LocationParseError(f"{target!r} is not a valid request URI") + + path, query = match.groups() + encoded_target = _encode_invalid_chars(path, _PATH_CHARS) + if query is not None: + query = _encode_invalid_chars(query, _QUERY_CHARS) + encoded_target += "?" + query + return encoded_target + + +def parse_url(url: str) -> Url: + """ + Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is + performed to parse incomplete urls. Fields not provided will be None. + This parser is RFC 3986 and RFC 6874 compliant. + + The parser logic and helper functions are based heavily on + work done in the ``rfc3986`` module. + + :param str url: URL to parse into a :class:`.Url` namedtuple. + + Partly backwards-compatible with :mod:`urllib.parse`. + + Example: + + .. code-block:: python + + import urllib3 + + print( urllib3.util.parse_url('http://google.com/mail/')) + # Url(scheme='http', host='google.com', port=None, path='/mail/', ...) + + print( urllib3.util.parse_url('google.com:80')) + # Url(scheme=None, host='google.com', port=80, path=None, ...) + + print( urllib3.util.parse_url('/foo?bar')) + # Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) + """ + if not url: + # Empty + return Url() + + source_url = url + if not _SCHEME_RE.search(url): + url = "//" + url + + scheme: str | None + authority: str | None + auth: str | None + host: str | None + port: str | None + port_int: int | None + path: str | None + query: str | None + fragment: str | None + + try: + scheme, authority, path, query, fragment = _URI_RE.match(url).groups() # type: ignore[union-attr] + normalize_uri = scheme is None or scheme.lower() in _NORMALIZABLE_SCHEMES + + if scheme: + scheme = scheme.lower() + + if authority: + auth, _, host_port = authority.rpartition("@") + auth = auth or None + host, port = _HOST_PORT_RE.match(host_port).groups() # type: ignore[union-attr] + if auth and normalize_uri: + auth = _encode_invalid_chars(auth, _USERINFO_CHARS) + if port == "": + port = None + else: + auth, host, port = None, None, None + + if port is not None: + port_int = int(port) + if not (0 <= port_int <= 65535): + raise LocationParseError(url) + else: + port_int = None + + host = _normalize_host(host, scheme) + + if normalize_uri and path: + path = _remove_path_dot_segments(path) + path = _encode_invalid_chars(path, _PATH_CHARS) + if normalize_uri and query: + query = _encode_invalid_chars(query, _QUERY_CHARS) + if normalize_uri and fragment: + fragment = _encode_invalid_chars(fragment, _FRAGMENT_CHARS) + + except (ValueError, AttributeError) as e: + raise LocationParseError(source_url) from e + + # For the sake of backwards compatibility we put empty + # string values for path if there are any defined values + # beyond the path in the URL. + # TODO: Remove this when we break backwards compatibility. + if not path: + if query is not None or fragment is not None: + path = "" + else: + path = None + + return Url( + scheme=scheme, + auth=auth, + host=host, + port=port_int, + path=path, + query=query, + fragment=fragment, + ) diff --git a/aws/lambda_demo/urllib3/util/util.py b/aws/lambda_demo/urllib3/util/util.py new file mode 100644 index 000000000..35c77e402 --- /dev/null +++ b/aws/lambda_demo/urllib3/util/util.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +import typing +from types import TracebackType + + +def to_bytes( + x: str | bytes, encoding: str | None = None, errors: str | None = None +) -> bytes: + if isinstance(x, bytes): + return x + elif not isinstance(x, str): + raise TypeError(f"not expecting type {type(x).__name__}") + if encoding or errors: + return x.encode(encoding or "utf-8", errors=errors or "strict") + return x.encode() + + +def to_str( + x: str | bytes, encoding: str | None = None, errors: str | None = None +) -> str: + if isinstance(x, str): + return x + elif not isinstance(x, bytes): + raise TypeError(f"not expecting type {type(x).__name__}") + if encoding or errors: + return x.decode(encoding or "utf-8", errors=errors or "strict") + return x.decode() + + +def reraise( + tp: type[BaseException] | None, + value: BaseException, + tb: TracebackType | None = None, +) -> typing.NoReturn: + try: + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None # type: ignore[assignment] + tb = None diff --git a/aws/lambda_demo/urllib3/util/wait.py b/aws/lambda_demo/urllib3/util/wait.py new file mode 100644 index 000000000..aeca0c7ad --- /dev/null +++ b/aws/lambda_demo/urllib3/util/wait.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +import select +import socket +from functools import partial + +__all__ = ["wait_for_read", "wait_for_write"] + + +# How should we wait on sockets? +# +# There are two types of APIs you can use for waiting on sockets: the fancy +# modern stateful APIs like epoll/kqueue, and the older stateless APIs like +# select/poll. The stateful APIs are more efficient when you have a lots of +# sockets to keep track of, because you can set them up once and then use them +# lots of times. But we only ever want to wait on a single socket at a time +# and don't want to keep track of state, so the stateless APIs are actually +# more efficient. So we want to use select() or poll(). +# +# Now, how do we choose between select() and poll()? On traditional Unixes, +# select() has a strange calling convention that makes it slow, or fail +# altogether, for high-numbered file descriptors. The point of poll() is to fix +# that, so on Unixes, we prefer poll(). +# +# On Windows, there is no poll() (or at least Python doesn't provide a wrapper +# for it), but that's OK, because on Windows, select() doesn't have this +# strange calling convention; plain select() works fine. +# +# So: on Windows we use select(), and everywhere else we use poll(). We also +# fall back to select() in case poll() is somehow broken or missing. + + +def select_wait_for_socket( + sock: socket.socket, + read: bool = False, + write: bool = False, + timeout: float | None = None, +) -> bool: + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + rcheck = [] + wcheck = [] + if read: + rcheck.append(sock) + if write: + wcheck.append(sock) + # When doing a non-blocking connect, most systems signal success by + # marking the socket writable. Windows, though, signals success by marked + # it as "exceptional". We paper over the difference by checking the write + # sockets for both conditions. (The stdlib selectors module does the same + # thing.) + fn = partial(select.select, rcheck, wcheck, wcheck) + rready, wready, xready = fn(timeout) + return bool(rready or wready or xready) + + +def poll_wait_for_socket( + sock: socket.socket, + read: bool = False, + write: bool = False, + timeout: float | None = None, +) -> bool: + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + mask = 0 + if read: + mask |= select.POLLIN + if write: + mask |= select.POLLOUT + poll_obj = select.poll() + poll_obj.register(sock, mask) + + # For some reason, poll() takes timeout in milliseconds + def do_poll(t: float | None) -> list[tuple[int, int]]: + if t is not None: + t *= 1000 + return poll_obj.poll(t) + + return bool(do_poll(timeout)) + + +def _have_working_poll() -> bool: + # Apparently some systems have a select.poll that fails as soon as you try + # to use it, either due to strange configuration or broken monkeypatching + # from libraries like eventlet/greenlet. + try: + poll_obj = select.poll() + poll_obj.poll(0) + except (AttributeError, OSError): + return False + else: + return True + + +def wait_for_socket( + sock: socket.socket, + read: bool = False, + write: bool = False, + timeout: float | None = None, +) -> bool: + # We delay choosing which implementation to use until the first time we're + # called. We could do it at import time, but then we might make the wrong + # decision if someone goes wild with monkeypatching select.poll after + # we're imported. + global wait_for_socket + if _have_working_poll(): + wait_for_socket = poll_wait_for_socket + elif hasattr(select, "select"): + wait_for_socket = select_wait_for_socket + return wait_for_socket(sock, read, write, timeout) + + +def wait_for_read(sock: socket.socket, timeout: float | None = None) -> bool: + """Waits for reading to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, read=True, timeout=timeout) + + +def wait_for_write(sock: socket.socket, timeout: float | None = None) -> bool: + """Waits for writing to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, write=True, timeout=timeout) diff --git a/aws/requirements.txt b/aws/requirements.txt new file mode 100644 index 000000000..621e27691 --- /dev/null +++ b/aws/requirements.txt @@ -0,0 +1,2 @@ +boto3>=1.26.79 +pytest>=7.2.1