
    6jI!                        U d Z ddlZddlZddlZddlmZ ddlmZmZm	Z	 ddl
mZmZ ddlmZmZ  ej        e          ZdZ e            Zeed<   d	Zd
ZdZdZdZddddddddddddddddZh dZde	e         fdZd edefd!Z d"ede!fd#Z"d$edee         fd%Z#deee$f         fd&Z%d'edefd(Z&d)ede!fd*Z'de!fd+Z(de!fd,Z)dS )-a  Opt-in WebUI extension hooks.

This module intentionally provides a small, self-hosted extension surface:
configured same-origin script/style injection plus sandboxed static file serving.
It is disabled by default and never executes or fetches third-party URLs.
    N)Path)DictListOptional)unquoteurlsplit)_security_headersj    _warned_urls/extensions/HERMES_WEBUI_EXTENSION_DIR"HERMES_WEBUI_EXTENSION_SCRIPT_URLS&HERMES_WEBUI_EXTENSION_STYLESHEET_URLS)r   z/static/text/cssapplication/javascript	text/htmlimage/svg+xmlz	image/pngz
image/jpegzimage/x-iconz	image/gifz
image/webpz	font/woffz
font/woff2zfont/ttfzfont/otfzapplication/wasm)cssjshtmlsvgpngjpgjpegicogifwebpwoffwoff2ttfotfwasm>   r   r   
text/plainr   r   returnc                      t          j        t          d                                          } | sdS t	          |                                                                           }|                                r|                                sdS |S )zReturn the configured extension directory, or None when disabled.

    A missing or non-directory path disables extensions instead of failing open.
    The startup docs encourage users to point this at a directory they control.
     N)	osgetenv_EXTENSION_DIR_ENVstripr   
expanduserresolveexistsis_dir)rawroots     $/root/hermes-webui/api/extensions.py_extension_rootr3   7   s{     )&
+
+
1
1
3
3C t99!!))++D;;==  tK    pathc                 d    | }t          d          D ]}t          |          }||k    r|c S |}|S )ab  Decode percent-encoding until stable so encoded dot-segments cannot hide.

    Iterates up to 10 times so even quadruple-encoded inputs like
    ``%2525252e%2525252e`` collapse to literal ``..`` and are rejected by
    the segment-level safety check downstream. URL strings stabilize in
    fewer than 5 iterations in practice; the cap is defensive.
    
   )ranger   )r5   previous_currents       r2   _fully_unquote_pathr<   F   sK     H2YY  (##hNNNOr4   valuec                      rt           fddD                       rdS t                     }|j        s|j        s|j        rdS t          |j                  t          fdt          D                       sdS t          D ]=}                    |          r&t          t          |          d                   c S >dS )zAllow only same-origin extension/static asset URLs.

    External schemes, protocol-relative URLs, fragments, arbitrary API paths, and
    encoded traversal are rejected so enabling extensions does not require
    loosening the CSP.
    c              3       K   | ]}|v V  	d S N ).0chr=   s     r2   	<genexpr>z%_is_safe_asset_url.<locals>.<genexpr>^   s'      ]]e]]]]]]r4   ) 
"'<>\Fc              3   B   K   | ]}                     |          V  d S r@   )
startswith)rB   prefixdecoded_paths     r2   rD   z%_is_safe_asset_url.<locals>.<genexpr>e   s1      UU6|&&v..UUUUUUr4   N)anyr   schemenetlocfragmentr<   r5   _ALLOWED_ASSET_PREFIXESrN   _is_safe_relative_pathlen)r=   parsedrO   rP   s   `  @r2   _is_safe_asset_urlrY   W   s      C]]]].\]]]]] ue__F}   u&v{33LUUUU=TUUUUU u) G G""6** 	G),s6{{}}*EFFFFF	G5r4   env_namec                    t          j        | d          }g }|                    d          D ]}|                                }|st	          |          rt|                    |           t          |          t          k    rF| t          vr;t          	                    |            t                              d| t                      nA|t          vr6t          	                    |           t                              d||            |S )Nr'   ,z-Extension URL list %s truncated at %d entrieszmRejected extension URL %r from %s (not a same-origin /extensions/ or /static/ path, or contains unsafe chars))r(   r)   splitr+   rY   appendrW   _MAX_URL_LISTr   add_logwarning)rZ   r0   urlsitemr=   s        r2   _read_url_listre   n   s   
)Hb
!
!CD		#  

 	e$$ 	KK4yyM)) <// $$X...LLG -    * ,&& U###LLKx  
 Kr4   c                      t                      du} | sdg g dS dt          t                    t          t                    dS )zAReturn public extension config without exposing filesystem paths.NF)enabledscript_urlsstylesheet_urlsT)r3   re   _EXTENSION_SCRIPT_URLS_ENV_EXTENSION_STYLESHEET_URLS_ENV)rg   s    r2   get_extension_configrl      sU    t+G L KKK%&@AA)*HII  r4   
index_htmlc                    t                      }|d         s| S | }d |d         D             }d |d         D             }|r>d}d                    |          dz   }||v r|                    |||z   d          }n||z   }|rAd	}d                    |          dz   }||v r|                    |||z   d          }n|dz   |z   }|S )
zInject configured extension tags into the app shell.

    Tags are inserted only when the extension directory is enabled. URLs are
    escaped even though they are already validated, keeping the renderer robust
    if validation rules evolve later.
    rg   c                 `    g | ]+}d                      t          j        |d                    ,S )z!<link rel="stylesheet" href="{}">Tquoteformatr   escaperB   urls     r2   
<listcomp>z)inject_extension_tags.<locals>.<listcomp>   sE        	,224;s$3O3O3OPP  r4   ri   c                 `    g | ]+}d                      t          j        |d                    ,S )z <script src="{}" defer></script>Trp   rr   ru   s     r2   rw   z)inject_extension_tags.<locals>.<listcomp>   sE        	+11$+c2N2N2NOO  r4   rh   z</head>rG      z</body>)rl   joinreplace)rm   configresultstylesheet_tagsscript_tagshead_markerblockbody_markers           r2   inject_extension_tagsr      s     "##F) F +,  O -(  K
  $		/**T1&  ^^K1DaHHFFV^F +		+&&-&  ^^K1DaHHFFd]U*FMr4   relc                     | rd| v sd| v rdS |                      d          D ] }|r|dv s|                    d          r dS !dS )NrE   rL   F/).z..r   T)r]   rN   )r   segments     r2   rV   rV      sn     &C--43;;u99S>>   	'[00G4F4Fs4K4K055 14r4   c                 .    t          | ddid           dS )Nerrorz	not foundi  )statusT)r
   )handlers    r2   
_not_foundr      s!    g%c22224r4   c                 t   t                      }|t          |           S t          |j        t	          t
                    d                   }t          |          st          |           S ||z                                  }	 |                    |           n# t          $ r t          |           cY S w xY w|
                                r|                                st          |           S t                              |j                                                            d          d          }|t"          v rd                    |          n|}	 |                                }n# t(          $ r t          |           cY S w xY w|                     d           |                     d|           |                     dd           |                     d	t/          t	          |                               t1          |            |                                  | j                            |           d
S )a  Serve a file from the configured extension directory.

    The function always returns True for /extensions/* requests: either a file
    response or a 404. It never reveals why a request failed, which avoids
    leaking local paths or extension configuration details.
    Nr   r$   z{}; charset=utf-8   zContent-TypezCache-Controlzno-storezContent-LengthT)r3   r   r   r5   rW   EXTENSION_ROUTE_PREFIXrV   r-   relative_to
ValueErrorr.   is_file_EXTENSION_MIMEgetsuffixlowerlstrip_TEXT_MIME_TYPESrs   
read_bytesOSErrorsend_responsesend_headerstrr	   end_headerswfilewrite)r   rX   r1   r   static_filect	ct_headerr0   s           r2   serve_extension_staticr      s7    D|'"""
&+c"899;;<
=
=C!#&& #'"""#:&&((K#%%%% # # #'"""""#  #{':':'<'< #'"""			[/5577>>sCC\	R	RB248H2H2H#**2...bI#$$&& # # #'"""""# #	222444(#c#hh--888gM4s$   B B65B6E+ +FF)*__doc__r   loggingr(   pathlibr   typingr   r   r   urllib.parser   r   api.helpersr	   r
   	getLogger__name__ra   r_   setr   __annotations__r   r*   rj   rk   rU   r   r   r3   r   r<   boolrY   re   objectrl   r   rV   r   r   rA   r4   r2   <module>r      s]       				       ' ' ' ' ' ' ' ' ' ' * * * * * * * * , , , , , , , ,w""  CEEc   ' 1 A !I 6  
" " fee $    c c    "c d    .S T#Y    @	d3;/ 	 	 	 	%c %c % % % %P     4    
&t & & & & & &r4   