|
22 | 22 | import httpx |
23 | 23 | from pydantic import Field |
24 | 24 |
|
25 | | -from adcp.exceptions import AdagentsNotFoundError, AdagentsTimeoutError, AdagentsValidationError |
| 25 | +from adcp.exceptions import ( |
| 26 | + AdagentsAccessBlockedError, |
| 27 | + AdagentsNotFoundError, |
| 28 | + AdagentsTimeoutError, |
| 29 | + AdagentsValidationError, |
| 30 | +) |
26 | 31 | from adcp.types.base import AdCPBaseModel |
27 | 32 | from adcp.validation import ValidationError, validate_adagents |
28 | 33 |
|
@@ -779,6 +784,9 @@ async def fetch_adagents( |
779 | 784 | Raises: |
780 | 785 | AdagentsNotFoundError: If adagents.json was not found via any |
781 | 786 | discovery path. |
| 787 | + AdagentsAccessBlockedError: If the publisher's CDN returns HTTP |
| 788 | + 403 with ``cf-mitigated: challenge`` (Cloudflare bot-management |
| 789 | + block). Subclass of ``AdagentsValidationError``. |
782 | 790 | AdagentsValidationError: If JSON is invalid, malformed, or |
783 | 791 | redirects exceed maximum depth or form a loop. |
784 | 792 | AdagentsTimeoutError: If request times out. |
@@ -885,6 +893,10 @@ async def _try_managerdomain_fallback( |
885 | 893 | ) |
886 | 894 | return data |
887 | 895 | except (AdagentsNotFoundError, AdagentsValidationError, AdagentsTimeoutError): |
| 896 | + # AdagentsAccessBlockedError (a AdagentsValidationError subclass) is |
| 897 | + # intentionally swallowed here: a bot-blocked manager domain is treated |
| 898 | + # as "not available", and fetch_adagents re-raises the original |
| 899 | + # AdagentsNotFoundError for the primary domain. |
888 | 900 | return None |
889 | 901 |
|
890 | 902 |
|
@@ -1077,6 +1089,12 @@ async def _fetch_adagents_url( |
1077 | 1089 | parsed = urlparse(url) |
1078 | 1090 | raise AdagentsNotFoundError(parsed.netloc) |
1079 | 1091 |
|
| 1092 | + if status_code == 403 and ( |
| 1093 | + response_headers.get("cf-mitigated", "").strip().lower() == "challenge" |
| 1094 | + ): |
| 1095 | + parsed = urlparse(url) |
| 1096 | + raise AdagentsAccessBlockedError(parsed.netloc) |
| 1097 | + |
1080 | 1098 | if status_code != 200: |
1081 | 1099 | raise AdagentsValidationError(f"Failed to fetch adagents.json: HTTP {status_code}") |
1082 | 1100 |
|
@@ -1925,7 +1943,10 @@ async def fetch_authorization_for_domain( |
1925 | 1943 | return (domain, AuthorizationContext(properties)) |
1926 | 1944 |
|
1927 | 1945 | except (AdagentsNotFoundError, AdagentsValidationError, AdagentsTimeoutError): |
1928 | | - # Silently skip domains with missing or invalid adagents.json |
| 1946 | + # Silently skip domains with missing or invalid adagents.json. |
| 1947 | + # AdagentsAccessBlockedError (AdagentsValidationError subclass) is |
| 1948 | + # intentionally swallowed: a bot-blocked domain is treated as |
| 1949 | + # authorization-unavailable, same as a missing file. |
1929 | 1950 | return (domain, None) |
1930 | 1951 |
|
1931 | 1952 | # Fetch all domains in parallel |
|
0 commit comments