
    kKj0                        U d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
 ddlmZ ddlZddlmZ  ee      Z ej$                  d      d	f ej$                  d
      dffZded<   dZ ej$                  d      ZdZd&dZ G d de      Z G d de      Z	 	 	 	 d'dZd(dZd)d*dZe
 G d d             Ze
 G d d             Z d+d,dZ!ddd d!d"	 	 	 	 	 	 	 	 	 	 	 d-d#Z"ddd d!ddd$	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d.d%Z#y)/zSSRF-safe HTTP utilities for FastMCP.

This module provides SSRF-protected HTTP fetching with:
- DNS resolution and IP validation before requests
- DNS pinning to prevent rebinding TOCTOU attacks
- Support for both CIMD and JWKS fetches
    )annotationsN)Mapping)	dataclass)urlparse)
get_loggerz64:ff9b::/96)            z64:ff9b:1::/48))      	   
   )r   r   r      )r   r   r   r	   r   zOtuple[tuple[ipaddress.IPv6Network, tuple[tuple[int, int, int, int], ...]], ...]NAT64_PREFIXESr   z0:0:0:0:ffff:0:0:0/96)s     ^s    ^c                    	 t        j                  |       }t        |t         j                        rd|  dS | S # t        $ r | cY S w xY w)aN  Format IP address for use in URL (bracket IPv6 addresses).

    IPv6 addresses must be bracketed in URLs to distinguish the address from
    the port separator. For example: https://[2001:db8::1]:443/path

    Args:
        ip_str: IP address string

    Returns:
        IP string suitable for URL (IPv6 addresses are bracketed)
    [])	ipaddress
ip_address
isinstanceIPv6Address
ValueErrorip_strips     i/Users/ahmed/devFolder/Ultron/claude-voice/.venv/lib/python3.12/site-packages/fastmcp/server/auth/ssrf.pyformat_ip_for_urlr   ,   sN    !!&)b)//0vha=  s   49 9 AAc                      e Zd ZdZy)	SSRFErrorz+Raised when an SSRF protection check fails.N__name__
__module____qualname____doc__     r   r!   r!   A   s    5r(   r!   c                      e Zd ZdZy)SSRFFetchErrorz"Raised when SSRF-safe fetch fails.Nr"   r'   r(   r   r*   r*   E   s    ,r(   r*   c                   t               }| j                  dfd| j                  r|j                  | j                         | j                  r|j                  | j                         | j
                  r"| j
                  \  }}|j                  ||f       | t        v r|j                   t                     t        D ]$  \  }}| |v s|j                  fd|D               & t        |       dz	  dk(  r3| j                  s'| j                  s|j                   t                     dd t        v r|j                   t                     |S )z>Return IPv4 addresses embedded in known IPv6 transition forms.c                R    t        j                  t        fd| D                    S )Nc              3  (   K   | ]	  }|     y wNr'   ).0ipackeds     r   	<genexpr>zA_embedded_ipv4_addresses.<locals>.from_offsets.<locals>.<genexpr>Q   s     *Fg6!9gs   )r   IPv4Addressbytes)offsetsr1   s    r   from_offsetsz._embedded_ipv4_addresses.<locals>.from_offsetsP   s    $$U*Fg*F%FGGr(   c              3  .   K   | ]  } |        y wr.   r'   )r/   r5   r6   s     r   r2   z+_embedded_ipv4_addresses.<locals>.<genexpr>_   s     R>l73>s       r      r	   )r5   ztuple[int, int, int, int]returnzipaddress.IPv4Address)setr1   ipv4_mappedadd	sixtofourteredoupdateIPV4_TRANSLATED_PREFIXLOW32_OFFSETSr   intis_loopbackis_unspecifiedISATAP_INTERFACE_IDS)r   
candidatesserverclientprefixoffset_optionsr6   r1   s         @@r   _embedded_ipv4_addressesrL   I   s    .1UJYYFH 
~~r~~&	||r||$	yy66*+	##|M23"0<R>RR #1 2w"}"..9J9J|M23a|++|M23r(   c                    	 t        j                  |       }t        |t         j                        rt        d t        |      D              ry|j                  sy|j                   S # t        $ r Y yw xY w)a{  Check if an IP address is allowed (must be globally routable unicast).

    Uses ip.is_global which catches:
    - Private (10.x, 172.16-31.x, 192.168.x)
    - Loopback (127.x, ::1)
    - Link-local (169.254.x, fe80::) - includes AWS metadata!
    - Reserved, unspecified
    - RFC6598 Carrier-Grade NAT (100.64.0.0/10) - can point to internal networks
    - IPv6 transition forms that embed blocked IPv4 targets

    Additionally blocks multicast addresses (not caught by is_global).

    Args:
        ip_str: IP address string to check

    Returns:
        True if the IP is allowed (public unicast internet), False if blocked
    Fc              3  F   K   | ]  }t        t        |               y wr.   )is_ip_allowedstr)r/   embedded_ips     r   r2   z is_ip_allowed.<locals>.<genexpr>   s%      
; c+.//;s   !)	r   r   r   r   r   anyrL   	is_globalis_multicastr   s     r   rO   rO   j   su    &!!&) "i++, 
7;
 
 <<   s   A' '	A32A3c                >   K   t        j                         }	 |j                  d fd       d{   }t        |D ch c]
  }|d   d    c}      }|st	        d        |S 7 5c c}w # t
        j                  $ r}t	        d  d|       |d}~ww xY ww)zResolve hostname to IP addresses using DNS.

    Args:
        hostname: Hostname to resolve
        port: Port number (used for getaddrinfo)

    Returns:
        List of resolved IP addresses

    Raises:
        SSRFError: If resolution fails
    Nc                 l    t        j                   t         j                  t         j                        S r.   )socketgetaddrinfo	AF_UNSPECSOCK_STREAM)hostnameports   r   <lambda>z"resolve_hostname.<locals>.<lambda>   s%    F&&$ 0 0&2D2Dr(      r   z)DNS resolution returned no addresses for zDNS resolution failed for : )asyncioget_running_looprun_in_executorlistr!   rW   gaierror)r[   r\   loopinfosinfoipses   ``     r   resolve_hostnamerj      s      ##%DM**
 
 5154DGAJ512GzRSS

 2 ?? M4XJbDE1LMsJ   BA0 A)A0 A+A0 (B)A0 +A0 0BBBBc                  D    e Zd ZU dZded<   ded<   ded<   ded<   ded	<   y
)ValidatedURLz9A URL that has been validated for SSRF with resolved IPs.rP   original_urlr[   rC   r\   path	list[str]resolved_ipsNr#   r$   r%   r&   __annotations__r'   r(   r   rl   rl      s     CM
I
Ir(   rl   c                  0    e Zd ZU dZded<   ded<   ded<   y)	SSRFFetchResponsez)Response payload from an SSRF-safe fetch.r4   contentrC   status_codezdict[str, str]headersNrq   r'   r(   r   rt   rt      s    3Nr(   rt   Fc                r  K   	 t        |       }|j                  dk7  rt        d|j                         |j
                  st        d      |r|j                  dv rt        d      |j                  xs |j
                  }|j                  xs d}t        ||       d{   }|D cg c]  }t        |      r| }}|rt        d	| d
      t        | |||j                  |j                  rd|j                  z   ndz   |      S # t        t        f$ r}t        d|       |d}~ww xY w7 c c}w w)a  Validate URL for SSRF and resolve to IPs.

    Args:
        url: URL to validate
        require_path: If True, require non-root path (for CIMD)

    Returns:
        ValidatedURL with resolved IPs

    Raises:
        SSRFError: If URL is invalid or resolves to blocked IPs
    zInvalid URL: NhttpszURL must use HTTPS, got: zURL must have a host) /zURL must have a non-root path  z(URL resolves to blocked IP address(es): zB. Private, loopback, link-local, and reserved IPs are not allowed.?rz   )rm   r[   r\   rn   rp   )r   r   AttributeErrorr!   schemenetlocrn   r[   r\   rj   rO   rl   query)	urlrequire_pathparsedri   r[   r\   rp   r   blockeds	            r   validate_urlr      sA    4# }}3FMM?CDD==.//y0788/&--H;;#D *(D99L(BLbb0ArLGB6wi @O P
 	

 [[&,,C&,,.BG! 3 ' 4-s+,!34  :BsM   D7D
 BD7!D0"D7*D2;D2?AD7
D-D((D--D72D7i   g      $@g      >@)r   max_sizetimeoutoverall_timeoutc               Z   K   t        | ||||dh       d{   }|j                  S 7 w)a  Fetch URL with comprehensive SSRF protection and DNS pinning.

    Security measures:
    1. HTTPS only
    2. DNS resolution with IP validation
    3. Connects to validated IP directly (DNS pinning prevents rebinding)
    4. Response size limit
    5. Redirects disabled
    6. Overall timeout

    Args:
        url: URL to fetch
        require_path: If True, require non-root path
        max_size: Maximum response size in bytes (default 5KB)
        timeout: Per-operation timeout in seconds
        overall_timeout: Overall timeout for entire operation

    Returns:
        Response body as bytes

    Raises:
        SSRFError: If SSRF validation fails
        SSRFFetchError: If fetch fails
       )r   r   r   r   allowed_status_codesN)ssrf_safe_fetch_responseru   )r   r   r   r   r   responses         r   ssrf_safe_fetchr      s?     @ .!'!U H s   +)+)r   r   r   r   request_headersr   c                 K   t        j                         }t        | |       d{   }d}	|xs dh}
|j                  D ]P  }t        j                         |z
  }||kD  rt	        d|        t        d||z
        }dt        |       d|j                   |j                   }t        j                  d| ||       d	|j                  i}|r1|j                         D ]  \  }}|j                         d
k(  r|||<     	 t        j                  t        j                   t#        ||      t#        ||      t#        ||      t#        ||            dd      4 d{   }|j%                  d||d|j                  i      4 d{   }t        j                         |z
  |kD  rt	        d|        |j&                  |
vrt	        d|j&                   d|        |j(                  j+                  d      }|r$	 t-        |      }||kD  rt	        d| d| d      	 g }d}|j1                         2 3 d{   }t        j                         |z
  |kD  rt	        d|        |t3        |      z  }||kD  rt	        d| d      |j5                  |       e |	;tA        |	t        j<                        rt	        d|        |	t	        d|  d|	       |	t	        d|  d       7 7 7 c# t.        $ r Y w xY w7 6 t7        dj9                  |      |j&                  t;        |j(                              cddd      d{  7   cddd      d{  7   c S # 1 d{  7  sw Y   nxY wddd      d{  7   n# 1 d{  7  sw Y   nxY wY# t        j<                  $ r}|}	Y d}~td}~wt        j>                  $ r}|}	Y d}~d}~ww xY ww)!zFetch URL with SSRF protection and return response metadata.

    This is equivalent to :func:`ssrf_safe_fetch` but returns response headers
    and status code, and supports conditional request headers.
    )r   Nr   zOverall timeout exceeded: g      ?zhttps://:z(SSRF-safe fetch: %s -> %s (pinned to %s)Hosthost)connectreadwritepoolFT)r   follow_redirectsverifyGETsni_hostname)rw   
extensionszHTTP z
 fetching zcontent-lengthzResponse too large: z bytes (max )r   zResponse too large: exceeded z bytesr(   )ru   rv   rw   zTimeout fetching zError fetching r_   z: no resolved IPs succeeded)!time	monotonicr   rp   r*   maxr   r\   rn   loggerdebugr[   itemslowerhttpxAsyncClientTimeoutminstreamrv   rw   getrC   r   aiter_byteslenappendrt   joindictTimeoutExceptionRequestErrorr   )r   r   r   r   r   r   r   
start_time	validated
last_errorexpected_statuses	pinned_ipelapsed	remaining
pinned_urlrw   keyvaluerI   r   content_lengthsizechunkstotalchunkri   s                             r   r   r     s	     !J #3\BBI#'J,5++	.."Z/_$ #=cU!CDD_w67	 (34Ainn5EinnEUV 	 	6		
 9--.-335
U99;&($	 6>	 !!!MM #GY 7 )4!'95 )4	 &+	 	 # .	0B0BC	   
 >>#j0?B(+EcU)KLL''/@@(51E1E0FjQTPU)VWW "*!1!1!5!56F!G!">2(?"0"6tfL
RS T#  + #+#7#7#9 )%~~'*4F,/I#-OPPSZ'Ex',;H:VL  MM%(U ,r j%"8"89 #4SE!:;Kse2j\BCS
?3%/JK
LLG CB	, & )#9 )HHV, ( 4 4 !1!12M  	 	 	  	 	 	 	 	l %% 	J!! 	J	s:  %OK!CO?ANK$N&NK'
NA/M8"K*M/K;3K94K;7AMAO$N'N*	K6	3M5K6	6M9K;;:M5NM
NNMNOM."M%#M.*N1N<M?=NN		N
N	NOO-N5/O5OOOOO)r   rP   r:   rP   )r   zipaddress.IPv6Addressr:   zset[ipaddress.IPv4Address])r   rP   r:   bool)r|   )r[   rP   r\   rC   r:   ro   )F)r   rP   r   r   r:   rl   )r   rP   r   r   r   rC   r   floatr   r   r:   r4   )r   rP   r   r   r   rC   r   r   r   r   r   zMapping[str, str] | Noner   zset[int] | Noner:   rt   )$r&   
__future__r   r`   r   rW   r   collections.abcr   dataclassesr   urllib.parser   r   fastmcp.utilities.loggingr   r#   r   IPv6Networkr   rr   rB   rA   rF   r   	Exceptionr!   r*   rL   rO   rj   rl   rt   r   r   r   r'   r(   r   <module>r      s   #     # ! !  0	H	
 Y>*,?@	./	
    !.../FG A *6	 6-Y -B#LM:      .h !(	( ( 	(
 ( ( (\ !04,0uM	uM uM 	uM
 uM uM .uM *uM uMr(   