
    PL
j{t              	           U d Z ddlZddlZddlZddlZddlZddlZddlZddlZddl	Z	ddl
Z
ddlZddlZddlZddlmZ  ej        e          ZdZde dZdZded	ed
efdZded	ed
efdZd
efdZdaedz  ez  ed<   dZ da!eed<    ej"                    Z#da$ej%        dz  ed<    e&            Z'e&e         ed<    ej"                    Z(deded
dfdZ)d;dZ*dZ+d
efdZ,d
efdZ-d
edz  fdZ.d
efdZ/d<defdZ0d Z1d
efdZ2d
edz  fd Z3d
efd!Z4d=d#ed$ed%efd&Z5d'ed(ed)ed
edz  fd*Z6d+ed'ed,ed
efd-Z7d.d/d0ed
e8edz  ef         fd1Z9d2ed
efd3Z:d2ed
efd4Z;d.d/d0efd5Z<d.d/d0efd6Z=d7Z>d8Z?d9ed
efd:Z@dS )>u  Tirith pre-exec security scanning wrapper.

Runs the tirith binary as a subprocess to scan commands for content-level
threats (homograph URLs, pipe-to-interpreter, terminal injection, etc.).

Exit code is the verdict source of truth:
  0 = allow, 1 = block, 2 = warn

JSON stdout enriches findings/summary but never overrides the verdict.
Operational failures (spawn error, timeout, unknown exit code) respect
the fail_open config setting. Programming errors propagate.

Auto-install: if tirith is not found on PATH or at the configured path,
it is automatically downloaded from GitHub releases to $HERMES_HOME/bin/tirith.
The download always verifies SHA-256 checksums.  When cosign is available on
PATH, provenance verification (GitHub Actions workflow signature) is also
performed.  If cosign is not installed, the download proceeds with SHA-256
verification only — still secure via HTTPS + checksum, just without supply
chain provenance proof.  Installation runs in a background thread so startup
never blocks.
    N)get_hermes_homezsheeki03/tirithz^https://github.com/z,/\.github/workflows/release\.yml@refs/tags/vz+https://token.actions.githubusercontent.comkeydefaultreturnc                 ^    t          j        |           }||S |                                dv S )N>   1yestrue)osgetenvlowerr   r   vals      9/home/kuhnn/.hermes/hermes-agent/tools/tirith_security.py	_env_boolr   3   s-    
)C..C
{99;;...    c                 v    t          j        |           }||S 	 t          |          S # t          $ r |cY S w xY w)N)r   r   int
ValueErrorr   s      r   _env_intr   :   sM    
)C..C
{3xx   s   ) 88c                     ddddd} 	 ddl m}  |                                di           pi }n# t          $ r i }Y nw xY wt	          d|                    d	| d	                             t          j        d
|                    d| d                             t          d|                    d| d                             t	          d|                    d| d                             dS )z@Load security settings from config.yaml, with env var overrides.Ttirith   )tirith_enabledtirith_pathtirith_timeouttirith_fail_openr   )load_configsecurityTIRITH_ENABLEDr   
TIRITH_BINr   TIRITH_TIMEOUTr   TIRITH_FAIL_OPENr   )hermes_cli.configr   get	Exceptionr   r   r   r   )defaultsr   cfgs      r   _load_security_configr)   D   s     	 H111111kmm
B//52    $$4cgg>NPXYiPj6k6kllysww}h}F]/^/^__"#3SWW=MxXhOi5j5jkk%&8#''BTV^_qVr:s:stt	  s   &0 ??_resolved_pathF _install_failure_reason_install_thread_warned_messagesmessagec                     t           5  | t          v r	 ddd           dS t                              |            ddd           n# 1 swxY w Y   t          j        |g|R   dS )z``logger.warning`` but at-most-once per ``key`` for the process
    lifetime. Used to avoid drowning the log when a fail-open tirith
    misconfiguration fires on every command.N)_warned_lockr.   addloggerwarning)r   r/   argss      r   
_warn_oncer6   q   s     
 " """"" " " " " " " " 	S!!!" " " " " " " " " " " " " " " N7"T""""""s   AAA
A
c                  x    t           5  t                                           ddd           dS # 1 swxY w Y   dS )u   Clear the warn-once dedupe set. Called when tirith is freshly
    (re)installed so a subsequent failure surfaces again — e.g. user
    deletes the binary mid-session.
    N)r1   r.   clear r   r   _reset_spawn_warning_stater:   |   s    
 
 ! !   ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !s   /33iQ c                  8    t          t                                S )zAReturn the Hermes home directory, respecting HERMES_HOME env var.)strr   r9   r   r   _get_hermes_homer=      s      !!!r   c                  Z    t           j                            t                      d          S )z3Return the path to the install-failure marker file.z.tirith-install-failed)r   pathjoinr=   r9   r   r   _failure_marker_pathrA      s     7<<(**,DEEEr   c                  f   	 t                      } t          j                            |           }t	          j                    |z
  t
          k    rdS t          | dd          5 }|                                                                cddd           S # 1 swxY w Y   dS # t          $ r Y dS w xY w)zRead the failure reason from the disk marker.

    Returns the reason string, or None if the marker doesn't exist or is
    older than _MARKER_TTL.
    Nrutf-8encoding)
rA   r   r?   getmtimetime_MARKER_TTLopenreadstripOSError)pmtimefs      r   _read_failure_reasonrQ      s     ""  ##IKK%K//4!S7+++ 	$q6688>>##	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$   tts<   AB" B" "&BB" BB" BB" "
B0/B0c                  ~    t                      } | dS | dk    r$t          j        d          rt                       dS dS )zCheck if a recent install failure was persisted to disk.

    Returns False (allowing retry) when:
    - No marker exists
    - Marker is older than _MARKER_TTL (24h)
    - Marker reason is 'cosign_missing' and cosign is now on PATH
    NFcosign_missingcosignT)rQ   shutilwhich_clear_install_failed)reasons    r   _is_install_failed_on_diskrY      sJ     "##F~u!!!fl8&<&<!u4r   rX   c                 ,   	 t                      }t          j        t          j                            |          d           t          |dd          5 }|                    |            ddd           dS # 1 swxY w Y   dS # t          $ r Y dS w xY w)a  Persist install failure to disk to avoid retry on next process.

    Args:
        reason: Short tag identifying the failure cause. Use "cosign_missing"
                when cosign is not on PATH so the marker can be auto-cleared
                once cosign becomes available.
    Texist_okwrD   rE   N)rA   r   makedirsr?   dirnamerJ   writerM   )rX   rN   rP   s      r   _mark_install_failedra      s     ""
BGOOA&&6666!S7+++ 	qGGFOOO	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	   s6   AB A8+B 8A<<B ?A< B 
BBc                      t                       	 t          j        t                                 dS # t          $ r Y dS w xY w)z3Remove the failure marker after successful install.N)r:   r   unlinkrA   rM   r9   r   r   rW   rW      sT    
    
	&(()))))   s    2 
A A c                      t           j                            t                      d          } t          j        | d           | S )z/Return $HERMES_HOME/bin, creating it if needed.binTr[   )r   r?   r@   r=   r^   )ds    r   _hermes_bin_dirrg      s8    
%''//AKD!!!!Hr   c                      t          j                    } t          j                                                    }| dk    rd}n	| dv rd}ndS |dv rd}n	|dv rd	}ndS | d
| S )u  Return the Rust target triple for the current platform, or None.

    Windows is intentionally unsupported — tirith does not ship a Windows
    build. Callers should treat `None` as "this platform will never have
    tirith" and silently fall back to pattern-matching guards.
    Darwinzapple-darwin>   LinuxAndroidzunknown-linux-gnuN>   amd64x86_64rm   >   arm64aarch64ro   -)platformsystemmachiner   )rr   rs   platarchs       r   _detect_targetrv      s     _F  &&((G 	'	'	'"t%%%	(	(	(tTr   c                  "    t                      duS )u  True when tirith ships a prebuilt binary for this OS+arch.

    Used by callers (CLI banner, etc.) to distinguish "tirith failed to
    install" from "tirith was never going to install here" — the latter
    is silent because there is nothing the user can do about it.
    N)rv   r9   r   r   is_platform_supportedrx      s     4''r   
   urldesttimeoutc                    t           j                            |           }t          j        d          }|r|                    dd|            t           j                            ||          5 }t          |d          5 }t          j	        ||           ddd           n# 1 swxY w Y   ddd           dS # 1 swxY w Y   dS )zDownload a URL to a local file.GITHUB_TOKENAuthorizationztoken )r|   wbN)
urllibrequestRequestr   r   
add_headerurlopenrJ   rU   copyfileobj)rz   r{   r|   reqtokenresprP   s          r   _download_filer      s<   
.
 
 
%
%CIn%%E :(8(8(8999			W		5	5 $tD$?O?O $ST4###$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $s6   0B;B#B;#B'	'B;*B'	+B;;B?B?checksums_pathsig_path	cert_pathc                    t          j        d          }|st                              d           dS 	 t	          j        |dd|d|dt          dt          | gd	d	d
          }|j        dk    rt                              d           d	S t          	                    d|j        |j
                                                   dS # t          t          j        f$ r&}t          	                    d|           Y d}~dS d}~ww xY w)uk  Verify cosign provenance signature on checksums.txt.

    Returns:
        True  — cosign verified successfully
        False — cosign found but verification failed
        None  — cosign not available (not on PATH, or execution failed)

    The caller treats both False and None as "abort auto-install" — only
    True allows the install to proceed.
    rT   zcosign not found on PATHNzverify-blobz--certificatez--signaturez--certificate-identity-regexpz--certificate-oidc-issuerT   capture_outputtextr|   r   z%cosign provenance verification passedz(cosign verification failed (exit %d): %sFzcosign execution failed: %s)rU   rV   r3   info
subprocessrun_COSIGN_IDENTITY_REGEXP_COSIGN_ISSUER
returncoder4   stderrrL   rM   TimeoutExpired)r   r   r   rT   resultexcs         r   _verify_cosignr     s#    \(##F .///t]iH,.E(.  

 

 

 !!KK?@@@4NNE +V]-@-@-B-BD D D5Z./   4c:::ttttts   AC 8C C>C99C>archive_patharchive_namec                   	 d}t          |d          5 		D ]S}|                                                    dd          }t          |          dk    r|d         |k    r
|d         } nTddd           n# 1 swxY w Y   |st                              d|           d	S t          j                    }t          | d
          5 	t          	fdd          D ]}|	                    |           	 ddd           n# 1 swxY w Y   |
                                }||k    rt                              d||           d	S dS )z4Verify SHA-256 of the archive against checksums.txt.NrD   rE   z        r   zNo checksum entry for %sFrbc                  .                          d          S )Ni    )rK   )rP   s   r   <lambda>z"_verify_checksum.<locals>.<lambda>@  s    !&&,, r   r   z&Checksum mismatch: expected %s, got %sT)rJ   rL   splitlenr3   r4   hashlibsha256iterupdate	hexdigest)
r   r   r   expectedlinepartsshachunkactualrP   s
            @r   _verify_checksumr   0  s   H	nw	/	/	/ 1 	 	DJJLL&&tQ//E5zzQ58|#;#; 8                1<@@@u
.

C	lD	!	! Q....44 	 	EJJu	               ]]__F?6RRRu4s$   AA88A<?A<,C>>DDTlog_failuresr   c                 	   | rt           j        nt           j        }t                      }|s@t                               dt          j                    t          j                               dS d| d}dt           d}t          j
        d          }	 t          j                            ||          }t          j                            |d	          }t          j                            |d
          }t          j                            |d          }	t                               d|           	 t          | d| |           t          | d|           n:# t          $ r-}
 |d|
           Y d}
~
t!          j        |d           dS d}
~
ww xY wd}t!          j        d          r	 t          | d|           t          | d|	           t'          |||	          }|du rd}n|du r$ |d           	 t!          j        |d           dS t                               d           nL# t          $ r%}
t                               d|
           Y d}
~
n"d}
~
ww xY wt                               d           t)          |||          s	 t!          j        |d           dS t+          j        |d          5 }|                                D ]P}|j        dk    s|j                            d           r)d!|j        v r1d|_        |                    ||            n0Q |d"           	 ddd           t!          j        |d           d#S 	 ddd           n# 1 swxY w Y   t          j                            |d          }t          j                            t7                      d          }	 t!          j        ||           nu# t:          $ rh 	 t!          j        ||           nN# t:          $ rA 	 t          j        |           n# t:          $ r Y nw xY wY Y t!          j        |d           d$S w xY wY nw xY wt          j         |t          j!        |          j"        tB          j#        z  tB          j$        z  tB          j%        z             |rd%nd&}t                               d'||           |d(ft!          j        |d           S # t!          j        |d           w xY w))a^  Download and install tirith to $HERMES_HOME/bin/tirith.

    Verifies provenance via cosign and SHA-256 checksum.
    Returns (installed_path, failure_reason).  On success failure_reason is "".
    failure_reason is a short tag used by the disk marker to decide if the
    failure is retryable (e.g. "cosign_missing" clears when cosign appears).
    z/tirith auto-install: unsupported platform %s/%s)Nunsupported_platformztirith-z.tar.gzzhttps://github.com/z/releases/latest/downloadztirith-install-)prefixzchecksums.txtzchecksums.txt.sigzchecksums.txt.pemu9   tirith not found — downloading latest release for %s.../z/checksums.txtztirith download failed: %sNT)ignore_errors)Ndownload_failedFrT   z/checksums.txt.sigz/checksums.txt.pemz=tirith install aborted: cosign provenance verification failed)Ncosign_verification_failedz5cosign execution failed, proceeding with SHA-256 onlyz?cosign artifacts unavailable (%s), proceeding with SHA-256 onlyu{   cosign not on PATH — installing tirith with SHA-256 verification only (install cosign for full supply chain verification))Nchecksum_failedzr:gzr   z/tirithz..z"tirith binary not found in archive)Nbinary_not_in_archive)Ncross_device_copy_failedzcosign + SHA-256zSHA-256 onlyztirith installed to %s (%s)r+   )&r3   r4   debugrv   r   rq   rr   rs   _REPOtempfilemkdtempr   r?   r@   r   r&   rU   rmtreerV   r   r   tarfilerJ   
getmembersnameendswithextractrg   moverM   copyrc   chmodstatst_modeS_IXUSRS_IXGRPS_IXOTH)r   logtargetr   base_urltmpdirr   r   r   r   r   cosign_verifiedcosign_resulttarmembersrcr{   verifications                     r   _install_tirithr   I  s    )
:&..flCF ,E_&&(8(:(:	< 	< 	<++,V,,,LEUEEEH%6777FT2w||FL99fo>>7<<(;<<GLL)<==	OQWXXX	+h7777FFFh666GGGG 	+ 	+ 	+C,c222****N 	fD111111S	+  <!! 	OY(>>>III(>>>	JJJ !/~x S S D((&*OO"e++ CWXXX=b 	fD111111[ KK WXXXX  d d d]_bccccccccd  KK N O O O  nlKK 	+*N 	fD111111K \,// 	53..** 	5 	5;(**fk.B.B9.M.M*v{** "*FKKK///E + 89994	5 	5 	5 	5 	5 	5J 	fD111111; 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 	5 gll68,,w||O--x88	8KT"""" 	8 	8 	88C&&&& 8 8 8IdOOOO   D77 	fD1111118 '&	8 	rwt}},t|;dlJT\YZZZ-<P)).14FFFRx 	fD11111fD11111s  BS .(E S 
F!F	-S 	FS (&H) 'S S )
I3IS I.S S 4A2M&S 
S MS MAS .O S 
P6O%$P6%
P00PP0
PP0PP0P6S /P00P63S 5P66A;S S configured_pathc                     | dk    S )zHReturn True if the user explicitly configured a non-default tirith path.r   r9   )r   s    r   _is_explicit_pathr     s    h&&r   c                    t           t           t          urt           S t          j                            |           }t          |           }t           t          u }|st                      st          a da|S |rt          j                            |          r#t          j	        |t          j
                  r|a |S t          j        |          }|r|a |S t                              d|            t          a da|S t          j        d          }|r|a dat                       |S t          j                            t#                      d          }t          j                            |          r3t          j	        |t          j
                  r|a dat                       |S |r6t          dk    r)t          j        d          rda dat                       d	}n|S t$          t$                                          r|S t)                      }|t+                      rt          a |a|S t-                      \  }}|r|a dat                       |S t          a |at/          |           |S )
u@  Resolve the tirith binary path, auto-installing if necessary.

    If the user explicitly set a path (anything other than the bare "tirith"
    default), that path is authoritative — we never fall through to
    auto-download a different binary.

    For the default "tirith":
    1. PATH lookup via shutil.which
    2. $HERMES_HOME/bin/tirith (previously auto-installed)
    3. Auto-install from GitHub releases → $HERMES_HOME/bin/tirith

    Failed installs are cached for the process lifetime (and persisted to
    disk for 24h) to avoid repeated network attempts.
    Nr   z6Configured tirith path %r not found; scanning disabledexplicit_path_missingr   r+   rS   rT   F)r*   _INSTALL_FAILEDr   r?   
expanduserr   rx   r,   isfileaccessX_OKrU   rV   r3   r4   rW   r@   rg   r-   is_aliverQ   rY   r   ra   )	r   expandedexplicitinstall_failedfound
hermes_bindisk_reason	installedrX   s	            r   _resolve_tirith_pathr     sv   $ !nO&K&Kw!!/22H 11H#6N  133 ("8  7>>(## 		(BG(D(D 	%NOX&& 	"NLOQ`aaa("9
 L""E "$o//::J	w~~j!! bi
BG&D&D #"$
  "&6666<;Q;Q6!N&(#!###"NNO
 "'?'?'A'A"
 '((K#=#?#?("-'))Iv ""$ %N$   Or   c                 8   t           5  t          	 ddd           dS t          j        d          }|r|ada	 ddd           dS t
          j                            t                      d          }t
          j        	                    |          r1t          j
        |t
          j                  r|ada	 ddd           dS t          |           \  }}|r|adat                       nt          a|at          |           ddd           dS # 1 swxY w Y   dS )z6Background thread target: download and install tirith.Nr   r+   r   )_install_lockr*   rU   rV   r,   r   r?   r@   rg   r   r   r   r   rW   r   ra   )r   r   r   r   rX   s        r   _background_installr   %  s    
 ) )%) ) ) ) ) ) ) ) X&& 	"N&(#) ) ) ) ) ) ) ) W\\/"3"3X>>
7>>*%% 	")J*H*H 	'N&(#!) ) ) ) ) ) ) )$ ,FFF	6 	)&N&(#!####,N&,# (((5) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )s$   	DDA/DA DDDc                 6   t                      }|d         sdS t          Wt          t          urIt          }t          j                            |          r!t          j        |t          j                  r|S dS t                      st          ada	dS |d         }t          |          }t          j                            |          }|rgt          j                            |          r#t          j        |t          j                  r|a|S t          j        |          }|r|a|S t          ada	dS t          j        d          }|r|ada	t                       |S t          j                            t!                      d          }t          j                            |          r3t          j        |t          j                  r|ada	t                       |S t          t          u r4t          dk    r't          j        d	          rdada	t                       ndS t#                      }|t%                      rt          a|a	dS t&          t&                                          s7t+          j        t.          d
| id          at&                                           dS )a  Ensure tirith is available, downloading in background if needed.

    Quick PATH/local checks are synchronous; network download runs in a
    daemon thread so startup never blocks. Safe to call multiple times.
    Returns the resolved path immediately if available, or None.
    r   Nr   r   r   r   r+   rS   rT   r   T)r   kwargsdaemon)r)   r*   r   r   r?   r   r   r   rx   r,   r   r   rU   rV   rW   r@   rg   rQ   rY   r-   r   	threadingThreadr   start)	r   r(   r?   r   r   r   r   r   r   s	            r   ensure_installedr   E  s     
!
!C  t !nO&K&K7>>$ 	BIdBG$<$< 	Kt
 !"" ("8t-(O 11Hw!!/22H  
7>>(## 		(BG(D(D 	%NOX&& 	"NL("9t L""E "$o//::J	w~~j!! bi
BG&D&D #"$ (("&6666<;Q;Q6!N&(#!####4
 '((K#=#?#?("-t o&>&>&@&@#*&"L1
 
 

 	4r   2   i  commandc           
         t                      }|d         sdg ddS t                      sdg ddS t          |d                   }|d         }|d         }|t          d	d
           |rdg ddS dg ddS 	 t	          j        |dddddd| gdd|          }n# t          $ r]}dt          |          j         dt          |dd           }t          |d|           |rdg d| dcY d}~S dg d| dcY d}~S d}~wt          j
        $ r+ t          d| d|           |rdg d| ddcY S dg d dcY S w xY w|j        }|d!k    rd}	nC|d"k    rd}	n:|d#k    rd$}	n1t                              d%|           |r
dg d&| d'dS dg d&| d(dS g }
d}	 |j                                        rt!          j        |j                  ni }|                    d)g           }|dt&                   }
|                    d*d          pddt(                   }nG# t           j        t,          f$ r. t                              d+           |	dk    rd,}n|	d$k    rd-}Y nw xY w|	|
|dS ).a@  Run tirith security scan on a command.

    Exit code determines action (0=allow, 1=block, 2=warn). JSON enriches
    findings/summary. Spawn failures and timeouts respect fail_open config.
    Programming errors propagate.

    Returns:
        {"action": "allow"|"warn"|"block", "findings": [...], "summary": str}
    r   allowr+   )actionfindingssummaryr   r   r   Ntirith_path_nonez/tirith path resolved to None; scanning disabledztirith path unavailableblockz%tirith path unavailable (fail-closed)checkz--jsonz--non-interactivez--shellposixz--Tr   ztirith_spawn_failed::errnoztirith spawn failed: %sztirith unavailable: z#tirith spawn failed (fail-closed): ztirith_timeout:ztirith timed out after %dsztirith timed out (zs)ztirith timed out (fail-closed)r   r   r   warnz'tirith returned unexpected exit code %dztirith exit code z (fail-open)z (fail-closed)r   r   z.tirith JSON parse failed, using exit code onlyz-security issue detected (details unavailable)z/security warning detected (details unavailable))r)   rx   r   r6   r   r   rM   type__name__getattrr   r   r3   r4   stdoutrL   jsonloadsr%   _MAX_FINDINGS_MAX_SUMMARY_LENJSONDecodeErrorAttributeErrorr   )r   r(   r   r|   	fail_openr   r   	spawn_key	exit_coder   r   r   dataraw_findingss                 r   check_command_securityr    s     
!
!C  B!rbAAA
 !"" B!rbAAA&s='9::K"#G&'I=	
 	
 	
  	]%2B[\\\!r>efff`'8-@w0
 
 
  k k k \499+=[[WVX@Y@Y[[	97=== 	`%2B^Y\B^B^________!r>idg>i>ijjjjjjjj$ ` ` `'g''(	
 	
 	

  	d%2BbW^BbBbBbccccc!r>^_____` !IA~~	a	a 	@)LLL 	o%2BmV_BmBmBmnnn!r>kR[>k>k>klll HGH,2M,?,?,A,AItz&-(((rxx
B///88Ir**0b2C3C2CD .1 H H HEFFFWEGGvGGH (wGGGsK   7 B 
D7"AC:&D7,C:4D7:2D7.D76D7A>H AII)r   N)r+   )ry   )A__doc__r   r  loggingr   rq   rU   r   r   r   r   r   rH   urllib.requestr   hermes_constantsr   	getLoggerr	  r3   r   r   r   r<   boolr   r   r   dictr)   r*   __annotations__r   r,   Lockr   r-   r   setr.   r1   r6   r:   rI   r=   rA   rQ   rY   ra   rW   rg   rv   rx   r   r   r   tupler   r   r   r   r   r  r  r  r9   r   r   <module>r#     s    ,    				                   , , , , , ,		8	$	$ gfff >/3 / /$ / / / /#      t    8 %)d
T! ( ( (!  ! ! ! 	  +/!D( / / / !SUU #c( " " "y~#C ## # # # # #! ! ! ! "# " " " "
Fc F F F F
cDj    "D    "     "	 	 	    d
    8(t ( ( ( ($ $ $3 $ $ $ $ $%3 %# %# %$QU+ % % % %P3  3 SW    2 -1 h2 h2 h2T h2U3:s?5K h2 h2 h2 h2V's 't ' ' ' '
i# i# i i i iX 15 ) ) ) ) ) ) )@ .2 W W Wd W W W W|  _HC _HD _H _H _H _H _H _Hr   