From 2f1a435dda702f66b9fe5aed01f21bfa7a833cae Mon Sep 17 00:00:00 2001 From: MeexReay Date: Thu, 6 Jun 2024 00:28:16 +0300 Subject: [PATCH] init commit --- Cargo.toml | 12 + README.md | 26 ++ conf.yml | 14 ++ explaination.png | Bin 0 -> 49021 bytes src/http_server.rs | 590 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 71 ++++++ src/main.rs | 116 +++++++++ 7 files changed, 829 insertions(+) create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 conf.yml create mode 100644 explaination.png create mode 100644 src/http_server.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cebaa88 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "http_rrs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +openssl = { version = "0.10.64", features = ["vendored"] } +yaml-rust = "0.4.5" +log = "0.4.21" +log4rs = "1.3.0" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f3af232 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# HttpRRS +HTTP request redirection system + +Default `conf.yml`: +```yml +http_host: localhost:80 +https_host: localhost:443 + +sites: + # - domain: example.com # Domain with SSL + # host: localhost:8080 + # ssl_cert: "/path/to/public/certificate.txt" + # ssl_key: "/path/to/private/key.txt" + + # - domain: sub.example.com # Domain with no SSL + # host: localhost:8081 + + - domain: localhost + host: localhost:8080 +``` + +## How it works + +This works as a proxy that redirects based on the Host header + +![explaination.png](explaination.png) diff --git a/conf.yml b/conf.yml new file mode 100644 index 0000000..7005125 --- /dev/null +++ b/conf.yml @@ -0,0 +1,14 @@ +http_host: localhost:80 +https_host: localhost:443 + +sites: + # - domain: example.com # Domain with SSL + # host: localhost:8080 + # ssl_cert: "/path/to/public/certificate.txt" + # ssl_key: "/path/to/private/key.txt" + + # - domain: sub.example.com # Domain with no SSL + # host: localhost:8081 + + - domain: localhost + host: localhost:8080 diff --git a/explaination.png b/explaination.png new file mode 100644 index 0000000000000000000000000000000000000000..217957e1b7216faf2892a7643e20b8611791737a GIT binary patch literal 49021 zcmc$_WmHvd8#Ri6q=bMX4U*E`xj{mtOS-$eOX>lnMY_8sq)U+Q?vn2AI`{S+=f@dm zjPLK~K;5jp;*KlloGVmbRsszL9|Z;m22Dy*Oc4eKt_cPP78?l`9Fb~I{Q>?%wv$wM zgn>c-1O59Vo(`P=28I+yN=!uAE%l(qGZp)?<@p&op~VjtWxs!uqMy(#$cesBNp9*Uiqn`9^(t0gQPH6jjq5~Oi*bfLihul<<;-W! zCgXu0*wnH{ob3Njr1LXC=(Fc0e3%gQzWYYN#TbW1Ea2gEwAfIn2nWs}r3NRMj!jR$ zMGp~rx>-M5sH4^>XlZ$@9ueH%+0l~*gSgo($SBupIBWNZd&u9hKtw0J*@%;KIhYkm zIG(Ls_eUg76!LwNAn~rTS>iokMuk4uk4%^);P^lB9LMcp1}3Jqi*ad6DUBMl7>Bf_ zCfDb`xNV8d`mjoa1Y9^68j+^<8>J9g&%L1dn&|j74K3a4HBJa;sJfC!8 zBrs@lT1*yhZJ9uN{a(Q3%Vo$SC9#{2_r{Pjc=`UD>MmH5*3FH+I1HhOlW%xz22j?}wvz;o6 zbPI+*8+&zi6@Z9g(N)du8#S^;5A5e15kRu;|rhW7)&QvJgoLiJty=I_$E+T2V9t{FIl_+x*0^GxoFH zohniD_I~7;(VAFX%94$% zyZbGev7)y2<5~!bj?e8T*jmrOq0~rZhlhtLoYvVTXn3r-d3iyYXI;3ui*D-?0-jd~ z69pJ#MnlOQ;7%F*?hc$Qod2f7N?3UQB7G=@i%GL~aLvw*KRukOH4$@sdAZe~3*~M` zr^VxPe>;LvHTI#qLCjY8gsMC)md%R^$GcxLU$Uw=-=|zdWyP8s~S$a)9MY zl1v;?Q_H1$?U&Udk=3pE@TK7(K_925nVI+k`T=GXR?y_%2bdb*DgQnRqtg7J51C`3 z1U>k)iTbY`M|By@5fec#^V28QM$Z+c!8!A2!KYhohQ?9hWj}m2=9k}-pwEit%9Q4m zjF6`hLqZPeoGm^h@D# zI5mXPa79M2t#16o7JYBtyqSBaCQiuXU^G>%LPSI~NBnPE2dFq~qaW$t4z%5!UL7uw z_&%IU#Zif{KqG-L9F0k4xG*UxeW-YjI$o&%E8zLv1h$i3^ap-0P|K!DUf@6<_7WC{ zO;VNyNAZs3JNA9e`PP9{0wZ!8tV9%X8lUUY?nHq~26S$jcI|^g(Scv+sU+9*m=iBl zh@*OHUMF=7r}5QUFKjvSGPT|qKzbvI_|m=3`fks+Jhpwd1xgjqvue!S0^ZIB;%TY#k1rItn)|VV6E$m!e zZKvG?N(Hh4J`ZlSJq90QB*ngD7Y9H3v{81l1jz|KvJ;~FUd@@3i+?k-v7ril^^RHZ z?qWAE8mvYHA&;V4qvcFFb-J6u!_6r$TFqLkIS_+G&*y+nPB38DI&INIj> zyz+SDI_IegeK<1$jY-SJ@yg1YUGw#_XQ--~t?lu_!RHHhTY2j(>RC=B^;$bI1ZJL=gSJ;*ANg>!o$Ll$S5f(ySlnMCj8+Lb$lN$1Ea^s$3X9U2}?Tm`bj=x>u z3kwU_0z=wv(&Fzw(6h7mH6-%f^)d8Qz#PDV(C+eu>dRa{F93f+*K#K-`q^~N~k!ovjccG4nEVryI z0`qJ&00W(n+pag7%%{!wc)4Yc_+RW|eHq4Mj7}o>a8NxGNi1+zRFIynZm0{kxypDz zHbbz*s4o`mi?W*sFoCwmLt73G4v=Wv)`D=mqez}-4WmG2Vp=#RCo-r^bO;^T{F_i% zyw}{bW*5!Y*ic=4{&au5(&)TjuH6K3gU`*$npG4!bmVw1x>8xe9^UWD`8L|Eb?82w zC4gVa1HZjvF*w`k3ycPP;XIxz)iD8d0&sO0QFQihepsOAiDhV+W%Vh$E>tI8c%qwfnsQH`qAa8~}E1 zGBpeJZ9NqE!4Hm?Q)(cKOwd$`iJllkW3$@lw8?n+~f9tX1F9Cc1!Jzr|_bQli>%*Be zq!+|dT>39RffrU9_RwZ1dR!eefK~n&{&vapxCJ~l7d6B4r~$fpAPvq{83S8pY|;4_ z{Eoh&Q>puzurj}}cJva=ke8$iq)CV-bYhOe=K#50;h>208aRc8r6smN9@s93x+;l) zN6i>HG&2cvibU|Bj;1_y1oH4v4 z&h6$T>ordzOGr3oCC(}O8oQJ7~TLJ#`aM@u6D*Mwvnc?mjqH|tTz zWSzhqxb0R)e4oz2;?iX({-9UKLJtY}y;`hNLhP}Vb(?xox2 zqfYq0VpkG*@PX}Be!NNs7K9L8;`!;3f$%l-CNRt}G$s=~pa77ev$U}x=5?Zp$Q#f$ zsQf~KMHU#?sZYit`eSo%Z_kQ{eYU+Sa^aAXJq23$N3XQU>+^y%%Ik4K}6bo94f0ux|Z{z1#*=UtmydJYz(wCY7gM9b^ z5u6m@2YwIG>egRY^Ybp{k5CgnI+L*Y8H|5JM5Zx`j?N{1<#KlO=%fG3jNL-SodXA@ zh;bxLQifv1Sbey`=3gpY^*2D+`(wZoQ6Bh7gnp;1qqK~wxJT3R5qcY#IAPV`7Zk-# ztPaB?pGpS$H}QG7-@Q6B7~8*moB8>|jsMh5%f0e761=<#G?_c#PA0NJ80i5w3yW#V&3+t?yLQyh{Q>4t75aOf`oFUnQ>g?>V zx?fq@z=_dKncb;^`3M}NpI35A@Q&W74zH0HO?Je8UHC@#B2RlZy`a*L`H)8vdRGb% z4rP?L-H`X>r&da>;uDa6>qQGPi}9AcO;i#Pg-TaN=@Lu{NRj@_C|Q@xQrW;U*pa6q`hmCM zocMQ^s|fKgw?NWgOTqe^B$_4f5gaHswW(##UB7f>g#itSFiEf=E;omM#mrub(wF-l zJBi7%=h9@XZUW(N38uoIa*CkkOF`=skL}`R|FjA&5GIBJSFG733e>i?TXA>1gkhm^ z5yg`v3>8xpP~AEUYPC$v7woHI)CI-|SEJyycAia-LtD!=-b!Mv&56>)#|v z_k~$Z#bm#j|5}sY>Q)sNAjUX0K@aH+CS;tldzGVDetYLs{w42U@aF6_l*JPa<-v$d zm$D7%l10V;E;w2wrs6%Q%tF2NHQIAXvTs+vmlFOMCjMuBdKY#63=wPD5p&hNmt0{uVTYvh2(5DT7fcW(_g{jh5iUuvl zwiVmeNPgldJHl6$63`$>S^)fICHs4DPIq3dVPPoM4DY=yvARt)p<1&-se;i`;p-9lOE z`-LH|*e23%&{gYI$V^lUIR0R*S;#t3C~!Vh;x1pWVt`E!4O=i(NaLG|q&O&Zi_5eF z4*bM3KL)DkOQvK08J-_)AnWu0?&!?Kq)Y<-WlI-7gJHTrSjYE5;uKi^nxr2fFuK9n4cXbS z6Nq4JeuN;T_+ol!k=yf#ekY@drQjiqtPvQ3J{`JR=kUg22>7g~k;SNsn+_uE&=J3U ziQ||~>O?Wd$n43V&K=SxI-s8_Yx;i7>QN7RT{Hu@?#1S3!OyI0D74A9KY7Ad{h{k1 zObpkRih;y-i}lgS@8Vw~llzbi{YIV?ECSru06YfiL6~glR?&RPOmrk3gj9cnW|;pu zKL9y+wqI{O=u>65x9>xzheHK|o54t^?oYJYuvZK4&|~dX;9j7Wc{4{0dxOrd3J;E* z!O?6r!)i-|k1#N=5W{$0VmfH8=2~~f7~@exGf8xlFiQapm5={LVPRo=A9R`cVR$etAP-(~7Y%fP2*XbZ4#Ru_mTm!j0F2oP zEw>9gyC7t6Nbl(_^lRZP(6IY4j;HU0n(=_&NoV}Q24Sw~6F-QJ=rI>Q2mSX0`g8{( zFyi-^NJgIjS}>Sf1RO^lGAXS7L8`S&k7@zvtYC}~X|}xJj7?Ba-1xz4p@&N0!g#!` z#;@e$ZDT2dAX&g_bSpa%|2dV_7=Rsmt>4G|M?~fPGC*XRZS~S4;5X@BCPIhW5y+K) zC9NRzZzopH#}8c=)-0hn%mq$-DE(;VsJJ5!f2Sp3- zL!4q&qA^X%YmXM4wv`M`bkL9DpacSr40|dFWn z)vG*nUvNgzEAl8j=2~bFg;}A@2t8|a5OqTVIvx}pN8VXgmN=vAq*VL(S?gIb85YeP zDh#5z=e3@S6|eQE@J8H{a?+uPatEf7iOs&X`0T5ucNOKGZKnacR(cyYqy5fPIYgQN{M8zqf=gy{Kmx{IG;D{EAJ&Jkr zBVL<{-ccseE~gtMMrH-A)aI?jCM&ErHZY+L8u>vy{pUY$86&t6;=i1$JJ*ncAamB^ zF09X>3bX!e^ zlF20^M=3jeu1Rce4JHb#4|`@~huut_X7ZiaqS`E;va74(7*?9v+-J(I)6&uw+U9R= z+n((I8u8svj2R*dJ^v+!fy1p&ahFLxcJWEBb@JO>8=}=#Z#*!c0ZBMXrEb#?l zRE>&Di9*VuK`XyC&5gfR@Ap>#$3zkFO|RK`J>Rda)Z3GjiR}>2jR0y3rSjg6N!VV{9CG~eR(CrbZzqs*?dFq9wX)QBUK3DC&9K#KFt}XcbG5o%a(q5M ztlJ)AAmp#%x)|Fp4;M#oGP0`K91tkerPN5ox)u01!oO+)Rx%nS@B+A%OBvq^y%b@F z;m}h2{Mw{1ppoj1AP^uP0e~qVX#X=t)RdGQcSgy4h}?DT&N%7>&UR2!IDKjjZWjWh z^X+!31}Z-A`K~r>r@FRmrwa+5pQkmEcYVxzV_1wthKO;T;?;Girt2y()6`FCw&+! zCZp$?l4}O75kM1D1=5)HJ8*TqD)l=NHj}+Q;+I@ze9OL^bCMZt795w_YSccgcMS;X zG`E`HR;24p4k?HY-$XIdH`RydpptDYIu7&I`ug@1VFyN|BI+nV?;W6J_&V$}H&8i1<_XIMjNeZS#7UyXqiP)C;rJx?@9y|pJTlLBGi_%R2X|M8(nvgZ zt1u*4?NniC^?H(40{~(bQuAg%LOGtAkSTeJ6JAm4j z=)EsAxQSTja?ggBUBtp*cWyaZYSn6mNBMzvGH{^J`m zqt&1KV><~CB8lDo=vV^WY5)ao*+bizd&-a_1b!9gfX=13*a0?R)ibN_e?djU0 z!Mm&on4vAu9z9HCJUJcA){(snK_|lJb=m=4S8?N6q;`fbiw`Z?Mt^?_E`8$tP*#i6 z?G+Khf}9s_j>sge@cS6Mo}##=Mwblcqi%_(`=EHs9~2srTv?_kl2Xt>jq(;W#cTUj zE|SJaJ3DUIORfY21h|Y^H~n-KR`WHX&!A(}+~40{@z^g54i2uZ<=h;2nGq`WIgR(S zxTHK8VBkec1%FR(#`C0~Z+e8Tb|*s0)%n&u zL`RV>y!7=a&25p%*+#Z8wEqB`R-@gW%89LsVQ*`C^1^@sGj2<2y?jiB@+8Rv4 z)o~*u%WY_I2W&yx{nBxx>v607dN=6p1+HcdwHyD#mG)`Nkn%kr4g)~+=0F1IN1H%H znxN&-xB5E(*_FcgYNKRZTi)m4B4+%C$B?d^j*&KR0E5s9tpb^d_w1La+h(D3`N4SJ zKq$6#Mun!-^L&A=`t?!Q{nbIPTu4N*8J|zh`bFrMgMr#eL)`RZd>#XX1V(L*>+bKU zhGR@~s3AHI?#GVzr#aRdLE5pD-#PHtj6J#c_uzm`9Jj~C&SHb3F9={IPnWJ>o;BtZJL`wi0>cHM|!^Dkz4sk4YEbW#>NuZ)RT?rN=tjn5MFz4o}J%+_p~Y8 zOu)fSK;c9#u!Q$q+)q76CJVU3SXZNIYcJB;af(>gO{BdXkp>xq*$O(iTQrb=R~;H| z3YxFgL>cSq>MFFGYIAZ70aY-Nzy#o}l9L%b?q&eBgUvgMUs~^pf})rJ-vtPzxrN1i ztM@$|20yd!(?gWto!L^ObAO8#*joTLzLg1zQT@4~zEDnjI8= z0=JjjeoT?u-i@@vAT!QQ(h;nM7H=@Oh@j%Rd3^dly#nYZY&m`wpNAXg{b?`&E5J6hidf+PC2_FgJ76&cZ!+lzq5uU25Ik7_cHhSt-}w0W7&ZoE_X<08^*O*yObEbi zo#~uJS$1;nWtVF3tLCH5EdT9S7+V~(KdWoIwV$wvH!OdcI5iBxMNEHIkl8#4vDIy& zMH0pRUzNv$3{kD5kfJAH7_;J0|By5qvOrhj~y*u#7rfpsT}>Z!ARvXyKOAs%j>;2Ij@ps z*)oxFD{A+5BgIE>Klo6S6Q5HHY+3uVFbnnX?Q`Vpq)R%5OLoxv{2@@i=Er)$@kz1A*IU2_G%sc|P81Q@~4%8A-y|@wR*Yw8hf^oc61ZWToC_v$cgUA?U5ZU0kts%q6In zdLoGxm&V4(5p(!j2?Z@uhlSD{Z8FN2TGMD2yT-aWHmY9dbnkm9!OSKBs*Hya01H_M z%Z;F3BY8Rrsc3uhP(?uSd%5~BgMs7dkSbd+y4tyadDgQ3?zbMZIiE0X%st>|s!in} zy?_Jke3}H&r?{AQuT?VMG~GS8TbG+K5+s{eK&xhWogtI`*>nJX5)rqZUs(fqAe44% zczLezxJeg}E#O=|-1wZiH)LiVifo}7k9}yp-f*9XaA6P$g4*uJ#5*JT{rp1Yv*BFR_2R*Ny-k-e0D=jcKW|Rg-yYwMW{HAQfy2U*sqKOFcv#48)vsqAz|{MI1Ij-whAtae7V(G| z8-y=Cp8yu@bh$SL$f+Kd`lCdMO za|2nwx$h#r5MiHLc2NEEX^CFl8KQ45wVPNOhfD@)!SYMl46h~YsE4y48C|d1zd3ct zWGRBWE#3p&{h{f^0wY8$7G6V-tt$_%Lt{B1pFVkNxepBmM*m1#sA$o@OErDF!t5+= zZ~uWTS5v{>-o9x3hbWS{1`(m}=J)LPZ$z8BGmyx1kC!fI>h`_aT1cAt+755V{vaaY zU_RboyK)mAw_NLFznNcHF#i^{H}Uxf@CWH27Ectk?F)Rr>OKaHFVuYrxO}#ULF9i1 z5y0nRodkgFpDrl@;c9MX20HlS+mD&TG=t}Dd3l>x3%1v{UUf?@3m%&`vQFTF<14zPz)w+CC_ z`;?VqwYx+PFRGX5wk`4;+DO4$ciVaMnp(}f>cYR)k%M$_(C~cRua7QA(%bVpNHoNEI}eY?0&=dVYZYZh8putkjYJl(l;CXd^nsQUP-RV5eDl@!B_Q$W zW^U;;^^ z!E|`fxT87)8@F6BsI zKqM)!@C3cwnBVaOi2oNKHbgv%xCL})fO-JknQFr$GB(ws!vI4#E|X5Z-5TIEm<_vM z&9??(j@Q}hs{a7A1mM6p%tkYT8bIkj%|hC@Q{l317JCZM^OGo>LSi~zKgTb;6I)RO zU2CeqQjJqJiG*Vt!Hd0#1WbpG6aT=9&x08{PeU=Ch=S^kH*tC4YCg?@m{n^Tbl~?d z_i{KPInou&-nR9-%lE#l*S5!#1(mMyW?mO#5`ZYR%`V74qN4(w-slwTPDNkArA_*p~w1`g@sO4)o#It?1J9e&hM*^)6 z5WO=`3#t)6c7JOres2G5en6Bs1Ukb5J8r-$|FoK8ZQKA`4|bWLWfgG9T8&P-9YLtN zpj3m>(twn6N^5(5JXXs848%P>Q6#pOmY!QlriAD{fZ)W1NLpI%18eb~(RMwYt2Vtl zm;?EI1We zYCh=z;gQ7ipwj#Ps>p^PKZ28;z4>xlQ!t~VxHt^+Y=~>+0hnT%pf~Sj6bb=#U`<_} z@5ygO=^ym&dqoAa4BTi`GRexCn)icjBR}~(9~9Dna0i5BF00wheM1;^*zLBL+rIYp zEYF?5WNjfxZ$@v?8jT9Xncpd=krgQDAt4|Ni%ye5W)NxMTdX z@OlQ2DiH~Iuwx=AtEtUQPeZK{L*OEsLJD*tigh-BZ71RY)jTfEqIV zd)!Y}e+QxB4rd5uU}i6>kz&^0A2mr!OM|3lk})##%Tn!-&mS}go;PbDJrM-gaTyOA zjr-+>13)Uo?zlB*#@-0pJP^0Thbs}T;J4c@w|#*=G!0Yx83@FWO)4x?hXwBv2M}uTVpTUxvFG`4Hwb6b?>&MSl-_94jzX+f! zZ!F5q?YUwgh$0k}XUPICvs+xW#v#4p>-xkXbYl-XyiUs>pX44+QyuS}TwIQpu*n0x zT5XIs#YP%hhI0sRmw!zN>el^T(K>SUOmqDmgrIe2yHd3_JJv}q9&xOr?4<`rEPZib z_b9QI^3sGEh=4!|Aat`Da1l98o#ExM9>IkLX??oe2P}16Lqk%z%k5^u3YcO=^BD?< z!6~mF7ZdQBxM8nEZhHgJ*m691*Wc}x0GWpQeJ+&0Co(lR-v!-(1X9D*yrn0oL`u{u zB#7NN!syDQDa?VFb@Z2&mBmOJfwo$^(J6_=5aC~Eu&z4^gfc+Ea!c}jgXpcHsK^Q$ zw%pweAOuX~aol=-7>xhxqB8+QdWi_ePG;W**6_2$6$KCe`eTm)M?Wv??!*e z4pmyYIH{ehHlCKH%tR19TkEf`_+FI!DhU9LswSiB{Qj+sUADC*r`1B$ zZ|76WN5&oBb0Q(dU)L~v@~Wy-180^MEu0|jMMg$SQh=;fhY0}t7 z9<4Wj!!NCb0CF#zw6|xmyqjX7o)Cg!tg@NRiPufLeoZPGtC}3AIA1;Ur*B1#k%^WO zPiPlGUq33nMVW~$6*tOGhyVR{CL%HljRzxlGg_Wb3(Tpai9fV+H`ol9q}zME1M#A0ntZI*ZU(^ zA{G=%Ve&Z**9CALsGoAAJ+6;H0Fj$$n50uH|Lh#l?qT<&>G*WK*l-S_w3?Rv$70Vu z1?F=mW6)HDkheqt#Jz3h*rB0Wr^4TX2ir(JvS4`b*TSLvP)SShGMOh`48(WrlBomyXo?4F!Ou_C63LgFX6>se8?Bw(=KG{@L-f$QegnVD&K>C52+%(Cwi6T$D{v$JX zlrk$)6{X@^cOQ?9vnOu`5+6*`z~<{Kl99tDSFK0_W#Q=tP-lVI1k_KsY`$v%jG~nC z04@)031%t)HVY0BUHRxQlke?7PL$v$L(JZ!0qHwm7sS++8z5xmCmtzPulBht z(M1EAGLWZ>g|@-Jp1XMoNMv5EqhKg6ueE_N1?1-JkKyHw zd$hEFj`ZS#vp2eEYYOj z84MO@1r$@ISu;RI3*z_D{c&5yl0N{zlqj%(mT40d2cMCW*i0iW_;vtvGfGhJd(%nc za=iQqiiy_{9pGgEu}Q>awVM421Zz(Z+d?ZXo^@v*jDd7&%?=1JEFN}_mYS~ReD5ZJ zfDX>T9Y7x2AQIco*A%t3rXMAQ5%{bFj{sF#YUf&EX(<$T0TQF~mMe`-6Y`i|a5eDh zeh2;!zkdA!CtldI<2$GNt4#jGmGZE&ph`imWw*{+f5{-^VSq(G0}b0Um=@?NcHWrQ zZzWrTyfi0$_C~_xSOkZv_=-GD>4>#bW&zuU{ebPf!rYmr>O*QMZc>(SM@G89{5ED@ znCW6&h6D4v!8XU)bf}1OEc+usa@x-1plTr?>YJ&^I6-v}t^R=h+JYMWle&jUCwR`^)_VGjio9Fq^uXn(x-MyREWmyuUy+0Ag&jX7~+C z?q@Lg9Dlmb_v{XT&AO|H@jUB0SmFT+bjTp?q8wvl!u}fDPkHy?017LCv~vyMDk_vc zsmaN@`g%8@IlHJ-aPYTlTPc=9CIeC<1UGZx<1%V(nOQgBk+XcTK`_IZMTof?S0X0Wy;-lRY+_|3tl~3CWmH8V|O*6Hxmwa?INqoG40@?Ct7otjq zjLj#Id8yySz)bZ2{P+5|-p_`H1^^U$-{((U5>dQw60*jRX!-ndTZkEh#>9ZGp>*%8 zMoz;>B&OE99@uq0E1Vu@^HTWJgaRnrQ#R)_2*d0@>Qkx!vR2iEe)R!y>qS>7EUFV* zfby}5@oBAwK9&Y*0e3H2;BcC{)ojwry9J|S;b@8?C|m9&BKV@q>n&##Wj9myT&_j? zPnfMr1iz5ss_g_lad`QUKa4X*vYT`sh!iSjG*^|T;&l}X3>@bS=`s`B$TeGm?n z5xy#@$dy#4pJ`KX)R+NPeQh z`KmRHqWad-5eNT=#i{J&ag5H%yTX*BFC)$?vK}PrFMNnH6A#gSW5TfN=fpUJ4j11W zMPny?4vO6K)>%}0$@zQz)aWDozF3hZ+eO5VZ-EP&ipdk6RIE|2GX!1T6D?k(ITG?$ZQ|X&qQ{UjfJ63%m{I9M)C#cyoi?P5h}s}f zhb)M~ycAit^bwu634n;JzlF@$%dgUOl~kD&#f%mgd;e0=0F*M#=+>t$Aoj@TQtqTy zpjb*~b|($TZybG7U972Vce;-FLDb3Tx7Bz-C);rA?#zdXGMgV(+a5fufH?Pj!yH! zs+3i^+a^fWR?Dv5BE5Q&j=9*^)8S+G!dB{<9r-%{XXt5LV1UEqDfmC@Z4?+iQs-); zYsq+2Ck+Er-=XFyPI<_4OkXJL3+Vd}sh6HovP&mi#)(ONK&r?CFPYXzW z9Ke2Z{;$d5bKabx+5BiO?RUHw>(|$q*O^ZN{R3F&9IwmnHR4exd~`RBH_g9gffKuM zi9Pgs7Qa^f#JbAM7keMWfAcXb(*phAGYw9E)?LZOF3ywjC^oXGMFu?WUnQ)g2mS^^ z-W#d4mGFhb^1?#N+>{m_CIO`2Ht%p8wifQu+ak+{d<$ttxERwZcdGO}hurP`b<1Dn z=Yf|ZksT;CXI4Cvc?*pdzg$ep92(dqP+H8*3oKC?`N#7Oau@DwKY3aAK5LLS-2n}! zfRTc|Vp*!VEsl@3ID&d-fr*v%gk8q zcZz1taYP8n-@f8AoAER_>+X-gk#-jfV3#N^DJn@)&2h9WcEFp3-$z{l6ftN2m#AD5 zIAd{G$7}qB-BBB;#`6=uHH^)wu^es==XZqP?hY4A!8h~rUe+|!-wW+*aW~$AReUKe zzM0tH&k{d*E3c!f!&XLhVe;QK%M8~v7o*K&^=UquOn;D-Qn4=sp%byF(%N&70_Tf9 z9wYT1#OZpKo$x9B7c80#oYx@27f zp`6)OTi9IEE%iw8`BFullPO--)Ji|TyK34356u`a582TRxrtz^$MZGX9_9XOW2(~K zk_)xueT%&v={<`*_U^b|>7gIhw_k%b=?lH&*!Ra?NrWt!y#Ewy6x=oRiFHKu<&PM% zc~#jAfBX$gvI*GlG6EB*zIsbi-?5Sy`v#IQfHr^qR9RX1q-)EL+iDee2&IdDq zz=Ck@T!?HYr#Cryd%DMe82K@g5?Uzc`PZ;m97m7Eia2)RP}MpI#wy5S;8l8an>M;4 zWTzX#$~ap+%>dc?+PT&{BhRm4Wg?*%O96MjRVqI?rBs|4I$Ep2hPBXV)Kb+>|DR7Z zc45D9n(^dWm4gnuv}10tI~sm+{HDeU!Z&cpBwp1!zSvHUR-;kO6NdLS=P*M1%w!3b z8eaBryxC$)r62#z+w(R%`9V(VA}uOYxp7vnA6UI)-`^U+ZI({#mX?>l?VvQv3@qC1 znuBmVp@-;~-IboO@ZBePQmKwV)6Z&qoDdru`!^WmrUdo;9!jO7*RK37P+n4GnK*;# z!Irz?Ec}tpQ-iNsm4l!0wKCX@9lu574XSKnz(m)$o&sZAB@UR03jrsiOpKoZB=IsO1z<4i& z4kLcPXQ8AVD%Q?-#;~`nXg7I(*2ohPO!ex?j|TOyV^hRmP6eJ+G;n%IFe9phWI~yq zHZACnZp91`C!&h=z&LcXijWLcfwFWcg|~=Tba?;r?$IaiDv=?uB3{9D1B`&@_1>S6 z+lem>+mk|>@`g71t<&9I5$R3*;AfmL^l7HPUO|&cHL_@fgHE?jm9lDR)pL_$!XnXP z=BOnb?O8)5LDF5lq1);sjGrugL#e%I+OUe^o(TkLY6z4Yg=g=6QpZRJzDeBuW>h`I zbMaM%hI2c~%%y3mL&cvgA#<*;|0ltqSytJxBLs;3pav3m82AoxZ~smHoWgIJnaRlE z)&2T@q0?lQWb$1QlxG4`IN9@FoBk$wfhOcMwL#141J_hOOQwMiGq4&TLO zL2#NS|6j(_$EGK&`qXpWr}bI~+H{9tt#WFlQxM(RV1ufXdQu(TfT3LMSYg2D3Xc~v z%Xu5NfH0M&E-gJa*zj9=ovJ?LaFE3FG)NR_?)_86(vOz z>1~O7-EjtWKw&mWj)PL^L6)W(Up#o}#y@7JtnT2_pjF7VEr5=WYQ;kwMNJ%4tx>8F z^d*I~@cBD$1T}0}(jSdh6`}tk2Bg_>G__^YYBBtIxeA@$z8X4UQJIPeC&=T$TEM-8 zr|z#^@5!%*AMnB{LK&U*F7xVk1hYT9oO{t{_k#@PCDOghKP@qDON4xN(fB646jmA$ z(o!mynb-GV8h(M3UtcO_Qshh%&idIzSNln0zT8z82}gFwd1J={L43apbw5g_ODU^$ z)6jtN-nG1qy4);VU*_FFLgnS3?#h~`GzPv11|Cx`u+nrz<>kYRVj|&2KopJflH630 zdk2T*3x~t&-=1;W@SeeIX?fNEoiIN5_y(JM6tV>H=Q`__1bb$IC{!W;veRZW@e{sc z)}ZSI+|W;@e&giz?jG0NM&m5eFyg>=%s9m#&aq2oJkfMOX&!=q)uua0I9NqbKx4Ga z&yc4Qa3xUttp5V%>CbfZfC&0Pw>a=(+6$sV^P5{=$X{pt%*G8!-#jJ3TYe)a)JmIDtyhP&w+GLQxM1&0 z4fPM-&2^owCRI(VCU(CYC!uj;B5jCB2uJmFwiSJ$|Iz0PU;c_hZ-W3oXhE#V&Z{6+ zc_Epm;ONwG>$^kJ4&yZnv-*fyB=9BoqqE83(kV9b=qryA)rQ=@@79Wo zK`K8;I#A%`DoE){_X>y`G!bKrqcOoy=`OY3qF7l)lat+&^;UGwK^C)y zb*L?=nJ(-_du}lO0DfJuxeV{7S>I2O<1c@YVL*6?R4r;oQ6%L^fp16lpSdcU${isRmw!}2 zb7`vFS|-yZ4cxgy(n@&xnM<|_P0fGhye1A;#xQS&tjK{2$ZYH(w12wcwI2^XtjWa> z?}GJ*xl~uogAg1f@ye>W6&WoD3JHLq_`_Kk$-Y7BHpW@cH&8wCDm5t?%PV}>jyrYV zwnsPZ0;8n}-g4 zOKkLpCx7{je7}7QY0CI8g@OmZ=^>8d1IFqsLXG-bG(r;^;@tM(TsDtOqRn)rDD4xWZkv{F40X?Zd7sHdm zi2svDkx9;pI&V+ZpPVY)<=8sOmn3sS5aCXH{J=z~=CV$1+8B-0rJ;IfojCKe+L-!T z?J6;fE59+xLSEl^n)stIACY=aO)9&pyZp1rLD0nurtjDhA%|*)14*L=e{#_Rw1P17 zWaTW7RN*22&FudgjA(xqqwd5^w`Q3NDhr`c=~scSf3{?tlk~;O)3th>y66RTby>!> zk+ShN;FY;hQG+1#$KPp~=uBS>ejVL#1t?;2{cF!c$TV!XWK}npS z%%{N75Y`eXL^>~sDTvA>iphY;zKWKaRG_ZF1-n3MeQr5NNPmx$_@Q~aFCf&cHpo5f zOpYVoW=Wio|EO1^Ac}s17T+IT9Zo{OwC&w96lR^6sQrdQoNlDVwf?9dAI@0T30Azgl>WjTT|2jMps;Tz$ZpQI#GUZJA?^7S+CNM0R{ zHl916^78srBh$;4WT1Jbv+=WJ^wo;_a*Z9AG|RnLYqL<2-;(?#0kcp*)ye1K^vC=* z0+RUQjnBTAhw<^oh60L;=?oZqAdOR_Rm0mJu3i_$_!gu!#~=p`bC`FMCY;qJ>Ds5I#OU&X3h>mP7lnl16kmVA{AW7U1%+QeGf6T@JM~-rGx@2Hx=E6S)?Z zPsPD>!MkSvtpp7uWV$zr#0st@(TgTsz#ls&Q<85Oa?hU?2R}Yb-y(s79uic6?3tXS zzXl>I##$GPu9_+Xo6=4BjEV5TxRbK7KuP(JNz|+!`mFB*%0VSp!uluJco8jbb{O^; z#CjEGJb%tKzXfZVAUPzp#g)+WTjEB6w{@(YI&9-2e`78U&I}%K9%6jK@QwTPDbfAI z;XQKAPKj2!K3<|uy$3&W#)t&shi{{s4x8C3a3E(=gPfUkVCbR~!CEc#a!c%<)z&ri z&KC6reqeNOEz-n_m;c;Jc!LraR>o{yXMH!dVX}HVIXI_4HW09F0ZnF=KPKn9;(4_Z9`6z zpKYv|YKCPa+xY7EZHc&-^5(wFOC(&?G)=Z(WE(X-BKRzGJZWUwu0adGF4sJs?%gYt z^K++KzSH|h^p}t&4IwWfJ0;{G$N_B^5E&T)!Flsv#3Xrb?IB|6SR+ZbhFs9ACJf}! zCItM})8djcEd}a-Q0O}}%qGR%uI^rFU4a*-x2NaKf(+1YS?McKV5}1@llnv)^7+^` z)3eO-=j+TxCqO5DDHk8NB?-#dIo45r{4I{34y;UHZ+FZ*w0~=g4Z0K<;kv7Ks7H~a zftS!oQGzm51R;^Ck#iGZk*UH3Oyg<}C?1Z$!>w8NPTh)2N{R@#6HqM_Ln}>;X(!wV zqk)m@=G^tNN3Y#82=9DW@(Eoz=8}T#Hem61r1Gk-qG!uW9hYGPZzmV$Vmnvlu2jj> zd|&&BdmK@Fz}iVuYG*7tGNUF}-kj`neuVn&w0a! z5%fG+p~#}QSmu#`Sl7peJHyJb1Ey zZr*)NASV_;RLLzOvJTp*GhhD}S>F!K{Kihf7r;eg*|}VLLj3aOh#cZ)%^o_D`&chf z+k9B+<}X$^Y4*qo{-}!l!S@I(r!577`!AwevMQ>FEg1beKwu?C&w`M=)JFvp6omr0)1dnW&6c*~O zdJgi!4zs5C8k4G;QsHmotu!6qz z(EV*CMQlmDQ1z|PQhfE-8wb|MGGlbKPV71OvC@KzJV$)&XU+)g{UK?WS`K4aTuqVt zWQv#w&0E@5yWSP+ZR<@^0kh1kXaf;B=_zdSn|f>>Y^(SoNWt>iGk31Gl?!j$XG)E^ z;j}AGIa^&SS`)8F;s1xGuZ*ke`<^DGJ0+#NQ$f1BySqa=q`SMjyHk*qZjc56X^~E8 zp8fqjpZ^=)0QcN`?%8{KLy+$5GB`#N;e3efyd1VjS;M z7@g*6S@5K~%9hsohn=OPsoxAEU>C&Frzx?-m<#5so<0?OO41Xt&XQ|YeZTcPScCHc zsu_P@&VUn3M2}gsDF1?!x_eGO^P{*lt3m>H_4IQi&X1=Sk8#5OBj(?;qv+nk`IDoTKAo61}8HJmrZa$X8hOiIqZXsTrKu;DDvll^lqx z32bVS8}d+D4{Ms1hjA)O;^^htv@hbv+vJ#{IYX$<{J33jQbOp0H`!M*jQ>MlGuI<< zRrXIs3W~rziDt4NHOw3l{JZMhYUr& z>DB3F2Af-IK{dWMD~O`ZXVErl3%c(UV)r=l8vo;U{-F}jXOYl;!3}L4XC_fYm~SQr zU->^YM+qE_089;oH5YeKKV~Ja`fK%Yu-Nrn%E|1Y)*8C-acOzF=JwaVw3<e_l}F*4WQMZOumPSc_l|nr zx<>K0io;X56>P^pKC4F6bCLECW<)hTah^{VG&;yULG~(CU0B4ZaqL!9W>v^1?e1=R z)=Poz_&~6%zDStphpU9M#Jlpp)!c|_At=u9{oiV~G)C9$5&jK~L9t~Fw)9n1v{=G3 z{5dikp6sc*Ue*rE;NthwUeq3CD7dxQ&%%CbC;gLcZlmkJ2j2kk^zmLv!AmanrtU_C zDppIH3Y_=;zp;L)86)6yNe4^+lq-t6o!nFnMT^oXdd4zLv%vR?gvIjaSZFDfZt!o2 zx&lBpd!wPP(NBv&uM20MqZ7Xm+wXJvOF90GtupH-=TJfgSy}8Bn)AIR>3<6;xKfis zZ>Bzs&Z*=@VtfAs2bBS>e9k9BkP&@qY4_G!{e|^;v8Asf`Jia*R2w=@pxDV+{)=ZfFbO-e80{IP1e?$1L zjczR#Im6_BJl%gjVNORPhGYtG^ewtP9e^{2E65OjCB;jrq!G^KmFu2fOPst>msVwA zzZ5zSANGb<{t!f3HeiH`RZ4*CL*b(+U>?$}{O)ipFH%F#EudvP)v&6tO9Tqm(W-5; zKZkytx=#jEUu3ob!r-H>VT!yfABUU7VZ?@R?8+10vCtLq%XI%@TdL0gC0g7Jn`>F}DLE-FyM5g}oVcw3ZJZA3xIWny*3Z1z}x^kQeP4r^{>V+_bJQ)_* z3qUyf2x}KR>a0|hYby&F)dbMx$)L!~wh<6Z{JaxFdT^C#(M8~%wEg|m01xj%Lh^+wzJN!F7%ySB1j!I?0hE}9{jm|2@2+2%I*$YU!5_vE&$3*R!iaQem0W&Nu! z!~pm&z&p_7k)k_j(kMjQr}Jp;nfgO9uhfgG;}hc#O5CSm zUSOB546Do3pr}|}%WMt8$#}re5K?ptsiMQRqm&kBqu!242KlVD<7i)*Bm$4s^kKwbi^BxSll{8w7Ky5(c7df9@_-Had{5YO6&^Rc)i& zE@YCW{eUVOLfXpJywkaS%=KA9vvoNbeI{oc&*~ccaRn&Zb-0wF{eDyjDhb9kG@v6+ zwp}?`o7R~eys=$Prkw4Ajl)?)onB06g+qFmw8ysM%#Lk0NCTJOPY#=aD| zx4^2bk19b5GN#`fvK^Z7N=XK^Iwt>N?)-=6$;PtXB~Amd9%b=WW|>wl|A9s>5~7bF zBm%i8f>wdEQej|?!7iCg|vk#@^8xlN*tuv(`5wbO+CH5@kDCoy<5C_5Fql>0RR-FrI8v z?EUntC-lR2R{IS;ChLCmb*{nK;5&WUat6{}HB1xg>0|%Q`g{PcA&h!er~D$yPfrxG z6Opfm3%0_R_jIMT&Mf0n7JeQr4O8^1<2tQ*9&lG5xS9B|uBq1H>pLE_ukEJnHms-W z$dc+gv+XUw6!-Wz@8<)cfw@j9VWnWY?sSVI=S=?i>u#W&|0fG-u5y)}Qo-#3BW||W zu@rS8qlH`nCr-&k;0e0U#$K+M#++srMJCyiP1$0DfdAf!hW}CPkGd?q)QS(2ox1Zq zOOZ>)@#+(OdodoF!|RQZ4DpKKSnt9Aw6&j2qx^)U6i`7xqarekVn_D%#Qpoj((2JE z6~9_8f_9+d%B^?z@Bu!ON@P7Gk`xdRh$C@v&U;%Lb3kTMwkw zq%?>zm9=6k(Zo!V7JSHK5Jm85jqxX~mx&*m&<#ruXu7WB=ZUOzpm$5V%%2v++@_25 ze<7#C+%!op-eSa$JdE0l0&_0A6Xp}pO1`hUAMSB_DRa?r#Kq?3AE|lx6k;?k*Ou&( zhHT=bA>pYS_?RD1`vvWxt}{)AwOK(65zaSDjd`Nkyq}i5pgfW5^m*hpSP#~sKnHin zUu;z*dS3kjvmjMQpHMX(f-8~cw9MEjt|{T2iQQ(i{kA;3MW07kF~W|tA!sZ}wQfVK zL#C;*48^E51Aa(nGr(AwIK^sv_w)enoCCvt7-Cxy`Y%a?-Ezwt2~OpI=qr~%&_kGZ zv|S+JPmXv|t_eIpazr=(UN>A`T)31#;@pO&?)M|+m}{znH5b^(th8h}e8x{=wTE?` zCL-1ibFlC>3e7$rUn|FyPC`aL{v8YHx_m$txB^*-J>GQ`R&XP6y{7#2N5%T-ohP42 zw>EwmSQpYVsn)MnB`-`^VP=PAVKM+UZR;~B9eu~UIQ1yb;piu&GqS(m#HVXf< z;WiRLq)c~XwAUUOtACckFyE`yyAL(`RF}h5Wu$b=3Z`p zjJfCRkjtm>js0Yo9{Ns9(Yvv6mYrk$`}7&mTI*;BYAae1=R-`rFIjaig$s5(o^rQ& zxr7u#AfX?@MU1REkz8vc5!o(l-R4YHs*J|WwOqYPkq_J)*iN}biSOQv~TyJs&1V%G!Op$SlYX7=ajtqwO^28Iqj_91M#jS0@Tv+x7@Nn)Nb~Vxctj0X6x7BX2c_+9J))Dg>WfGjPbjGO{zU zV8y$6@KGq|z>K8*M;I%yYf14(zbYHXw?2gA3neBne)!FF^gpkd*PUcx;+Y>Fg0U<> z+2itw=+62_(g%(2>{pB90QNoqPO`D!jBr{OAaa|o=tLc|u()8Y%buaAW5p@;acIdH z>Hnkh52>kc=DDZJ5UkJ|vI=I{;6d@}UJW9j4O`3E>qqA67lQ)Hl2o_ z8I%EHh#NkKta8yWd6QWg6^|ZM5~~|og=(G10xUcjlQdJZmRXP^cSD+$*4x??;tCn0 z@Iq}$t-?S`kG0pdaWY{oRR7V{TPqU9wJKrO&MrNzhLl$G6iJX@!@?aU^N(q&x%LCi zB87BoQ=TsG&0xVbl=O~ly;yb;!!=vtvsLIrgtSXRs?KWO5?wARYUQ(njNs@c4%Ce) zbW0Mojp19qbDh>nVz-*9-wb-QF0=P?k!5&UPE3P^ss4|4Ce1~br1?X*H!A6<_{vPV z&5H&2&`3vl2USJ$ zVLnBAk?G~j15a*b)Cf>)p~F`!MIMocJ_MbJ0x>Ay=}F4-NX=q#lg1${^86xEMe4Sq z8>HBtUipm)6(OW{P-mb-P;&!)c`HO}iBvPCz8zbO{ZJxKy>O0YcQ5+YSyO!SiV~>@ zU&7luu|OjJN+Wo<=lrvXA~cf597T~8C3}HToJPdQU@4Z|nj(YkrVHcoQe9KC8IAVn z7Hylp=B6rXrbR=Z*q97w&1Um|dKYu!jJyvapA)rCGylla$fb(4e&LPDNt*x!A~Zs+ z!?>V-+lCb7bjk?%K4aHZFbcXzA{d71@TC!C8sw(wj0o?Y?GWZ z{dQyRHSD+vQWOl!qFX#26X?;R*hAG7t-PQlE%+_P6+$rI~8&{O9xTW{VlJd9x zW@z;@r=a1;Knt6=SAao~rpmavom4Nq3b!iN%ZK2cSrXVJX%F-vA;)^x;y0{*45N}Rla zTovW(tS0 z50zC`LZ;WhC0u09S*e5TwGw3t&--jtH6{te=ZQZ_eU33jzm`OZpaO{P{5V^rTO||) z_z8_4RaUHvDxE9yI9wCfq zE*O??&irt!J$E(!*&@}w>LG0^KY6LFWttyVn2=R#D!(C-AdYIb%)1}y8$D{>Z)5LC z5qLAt#W(r}?cI1-QCO#bXl;zQDtph$Lz$IY#W?^lzY81Pl{nUhNkJw0v({Gj>nR3% z!j?pu7rD%S(FUK3y=sjzaOVFCR%e5m!?m?uRBcqq27K6obPz6~6g$l)#p&!OC!kAO z7abL-I-GnKN-@W`M0h=~UoL(VsJOAYobNw05Ha+>FQeUAwS^daNo7?mv z$VvhGw-IEQRU3!QgVl?9Skobobb`I*Xy1xmub7)tY&8r}M1jI7km@l)y*IDwE9YJ3 zoJ@_%zZ_>Yci6rIwP4o1(=yY)g9Wt#J*<_g!J91a_+?S%Nxj`=a*t zzFFk$WS~XbF?%w!C`T$`)V-A8lB_FXufSCz7C#_n7Pnw^asFEfbPV#og^VxUieio5 zNDyZBbB&E!N-H$;yX=}u=dB}GM1`H22n+uAva6vax^0hQHvil#*%z*yr~+}SR%1yQ zKk*d}%-3Tu+Pf5Fpah)j!v$b^Y3SdkRLK!hI2@9GIuNEVq2Yb-)*>)dal$sR{mmzm zYxPO)ew4Lex+upXmDX@lD@DQNx@wJJrNeCW^SdVIInVCte5OdVij4v|rI`>-zdQ%> zb)1*9)n0o_>}JS<7`T>7yd&;)HYgSH4E{s6By5X?I^6A(sPpFlH$lPSFf?kD z-X-i;v)2_2qpxZYxTjic4_;a3ruweA1ao-!a{;TMX6K!^Zu5IN;t!S(EJ_GI2he$0;S|02)iM8b2gmfoXT7sHn zi=(76?>WMN5mO&KrL>8!1Sjyuz6reRUXVlQL!gtk|6s12m0FZ4P6v3GFQ0CR0>uC) zQ>upw8!QduP?S6VpU^5*)D2KPxhye3pph9lzO}gO$Pji%nmnlsRFn!>MggK#EEg_(E98NIjA&4#0e_YIHl{h>Bpt*2- zr(WHSYcr^UF-Iz}K?6fT4x}2uLrg3@_XAw~2o@joQ!;4RbvfrJ5&bI4&J+m0i(8tt z_tiWuPIOl4pxuNVrsI9KI~H$tccfMa_Q|YBOWEiU9KY8|GX7!m1HD@KK>tilQ-Q?+ zFie`OsB&1szr;WcRkNg}PK64BPR*f{x*p=zk-y0a6uioOKa~7~#D@^q8*<RcctkSZn-BrHmvAm^s9n#9aEsxzf`_n^m~1URbWzCYcDliovU= zxENDOUpdb*sE19AqwlyjRmyR_>RynN3ucmqW4$(omo)CPjyD7>Q)(y^r{qJ~2eX zNq+_mI#;;DmXA?jgAdGME{;Zj7%@^TGQL-x^E$|m#ebzO6$Xr^qPEBV5R(F|t0B7Y4Q=JDeUBczlO>=6xZodp*7ixV#j5Gm>3BY81teDK z9auV8%gz86rLBy8BA!>;b52g~x>*CrN^`4KQE)lmNr$Vnd;OmlAn9a&e46@dqarSb z(=Aazp`I|}?~!CZllt;|?6xngsfPT{2U{qM^)WVh($+hGWeWaQc0kJ?#KH1SRK&P3${s|@9xI|WJ9;W$MM)29i0>#j z$;L*O?rEZ6@~=bLBo4tQUfD$?tr*)`L-9%)tV~=E z#L&I5*8b$L$cUTF0;u%4bA;T}7HVud;1fVuX7mCa#_@%G(`iUY z?_SCg=1wf9csb598ylvg>vLU^2xhNxvEKZ0iY$gGFVxjrY^!2K^Us#!ru1f?ECV~o zJQqia>FuixROUo0&MHUO60JY-TL<4Z98|_!VSic|lc&3pBARpzWj+-i+55N*vX#^x zKr4f%Q?(+JWb~FEq5z_o<0$(dwk$?6+KLDBPD}lcS)F$^#w&&{j@EA-j^YreSeMnS zDi@s?EFC$nqEj)>s1?>TTSyaiJBI2MT1;eE4&H%E+9s{r9N!K5@H%1N>V9z!vIqIm zGK9oEU2H;kdfaV>oj~l#ht_@epq0k2xmyvKuXd``-l#t0~X~h+1<}( z>~v{N>G?D}#!WMSQ0tIQ8w7j=oL3UE>$8il!q)Bsm)hFY&;kWo%3licI2xFKdF9M? zH2|}2NDJS!#G+};jgx;*6XWBI5qC$-Tm6oQnKtleWJ_9wB1MNcvH0ImRXG!+au70^ z`OwhOib{rJhAPl>JoU6O|H)G)2MYDgIcK78ypLg%9>Ka4YmK)GGvN-)37MJs%(B02 zYh$f_zr&ELuOEdbT=uQ*;czB=xJ59`WU_BKLPnvOIvF0R=N<8vjmi8%3BzXwxu$g) z-bWqM)Is6U;r>Xm%Goa>m$_H)=6oLxQ$K3iKu3Psx0lB@loR5_^%aqEh1qNK_mr?Y z*~z23^0m@#${k!~eBi;PYUNTTw+7aaU^I-U&3$6Y7Y=Q10Q}&;*z@nci60#?^l5GN zZr>v_Wt>~O+pqh37Z*2xGO{e*ei}z2*ScrMjxXrrIujT?6*(AIi-S_GFp+N@lgm4Q zv)V?SD6dPN-A2T>W;CVoIjHO388l511s`Ks557vaoI&IC?&a*%dKNqC^YOyAvy7oX zZXKk*Aa<;|{M+KNqWW8J^S-d_6=vMkO(iLFw2l`u@CS z79*ei$?um`{N8qd@yP4-+w<)j$>f$!_t(=j`*oYUn2zhs_GO0zmW~dp%jwP;zUH^X z$9nor?Wa`|1D}U|k7cR6hqq-%KJT4{>T}6q{WCiJ&t*1-cK$EMvh74l5wRVv(T~6)=b?$n-Uh&7;%dbo`ym3l!rf$&mYdiS$3p0pkaPqd> zH#}e7I*;Sprl<9v?l^{{fYaGr_h-`@OXc{YvkfXV;4*gkmcg6Jwp3m5=X2;Fjf!hUJwPhopb4cQV+d zG!&-yiFkf{%Hagm0p(vbM^1 zEvF;4%%1#08_BZI{S$fU#1qzFC7#V)xIQnYv3UyNl!S=qg#FR-pq0u}XWf07&XZ%> zr324Y^L;$ujGMvC2mVfvV`_hk!30?n?Uudy`nGlt*Bg5JEM8;1J3@0&*LN9^U*6G+ z2xE3|j{TPEx0q|j19L5+#XGu1^TpfWQy^o zHF3V%L1$EbPX!6nPzj99Ru*OE!xGHEjm;_yr6toP(GyX7O>u}25y?D@B>lt>J9$UE zZ8Mkw>B9Ej)NVHP<;J%Qcy!)5&&bBVoEtvRbbyKJi@)*9gq`lwu;5`9O}x3|&}GH~ z)od3F!RDJHx#}L(XO+-;S6cy7nnYg#Lu>2GitHg+woLBk+4PAF+y{Zl4s2D)8$c?| z{ASaMr3o{$l5ERi46adA6~8Kxm*ArJ#YZ`Zsi3guw2WF66vPvBrPSQYtH1bDH?eOL zb7@t-w=D?#Id(-+Xv*|>EMmS^{Dex5X`HSr8RwWA&uY87JKN~o@Oeo&Bzf={q7*4l zuA~|eG_-{M2$4p1hnU!f9X~?V{ZgY%^%L&ukZXx!>0fLS1XVutGFD~Qp*2X9x&Gdy zJin@z9Z!PMC@fbNJ+9VmE&R*u2c^CPW74i|Ab;>V71L)!lk0uotDDN;YXkL%>UZOiIFp>%f8axBK>-tj-cnl zA4;#O&xX_tQ(bi_cpXm!$^oU5i@Du$0^41X_=a_m(mG>U@7wM}lrg2xNxi z5(O!}|NVWCFgqO|T|3`;WKpf}RC&dbXNuf#`|qEc9Wx|CbM|LghokrhdYl$h-&Pu| zAK*yzxh(edwqiBxda;DqES>_w*rRB(Oos4vZ<~57u#jPgxHxa8{LSoo-K??a!4TFij=Db(=2w zO+4QA2sLRvaKy0vD-FgsHN9dUij|~t(;NeD)5vA-qob3?MwUf~vlVKV25>wB!ac;L zpBx8MlS%D?chZ~$`%h%DLq-zP0t)i-^4v%)OiWzrXscxTj1;uM%u1RS545>qH^*!V zLZHcc=V|;&XHkh3VDO9e8f+ZGV!K=p29-qOKekzGYbW-TTphPrV1oNwbnO{6Ml#r1 z?cvte)>k_W%yyRGUPYgE-*2Qvw>Bg3(Id!82w*%u#1moPG-1*oGY}v+e=8nbvowiC zf}Y?g4{~iL>&(HtI`Qertc5x4I;~6?bV6x|R_>|1$Kg$5@!&3Zqcc0gXkyGHV@f&^ zVz6HV*At$8_f8AL+Cv4Z$JCp9%rrhst?RHzDu`%TlqE5NYnTx$Hw)|fj&^E01S~EVyxyk zM}f+it@Hd6_y;;*+u<^lJ-xiD^&hD1bJ=C5YwPJv2ERB1LB2oTgmqd>fC=*_3;Aqz z+qD$E`cTtZ3?qpFL0wh5bg9T%bjJ#8+^LSGZI}8>&tIqR(PVX?{#NWU+Z6(vGtU+c z(Ry1O?g(WWYz7s|C**LxDLqcfVr5en#JSi0n<$H6Fim^m-Tlj<(CQ~TQnl=?FI{G= z=U94b>e!*r{am9nR>A{Z0QS2K;X<-Y=5O$$YoABR}8RGNAk)&Akia;J7^ixL=+w)*(Ka5|4} z5Px8B_5*fVXVw7eW1nh6&HZkyWh|$5p%-vr6OdkBKU- z^x*^BYK>pU&_el~SQpDj&uU(|84^=w8$mEhaHIx-3lrq=b{aA$D~y`Z%Z5J6-U3(s zA|0`2hty)_L|y2Auz#%jG&D3M*rbZzk&wA3F@SyH%b5FsmQ$Jy{Gt2zeI|v9i%=f~ zb8~YO4)cc{h){JSi6*ozTlcfB17be zthrt>V$j&roo{xXCna!KF?nDzL@n zJ?_m0C(i923b6b~p}4sw+4sf?fb2_x%n2oX$x%;`k~@a3t4xVnKgB)wp)XUsgss#2vAC5!|X_-vTWTtPxoY!QmQ((*eET|Ma6D5_8j5ia)p$mPYgWpCq)A*9JtSeCIhv0?Zq2WfxoXG_iD!+D~P8veWE+jFnWHq%g0@Y~Ms+DFiQWSH05UId9lM#+Ct}uyO;Q+&dHa*N z(NjKCRiP92nANcZ<$ak!gdG57e7=Yza8J4N>{tn>G;~t9V0JLldpc!Wd_8uNDvN9b zS!Uvh)#nvz&`Cn1LdA4$-5Gy(F$WV_h2O53fV3ly+1P>fi#bGJMxh@vIHmbeTB!Z{ z63n5Du*BA@eW*Xm4rWxuLD`s;^_GOS2pk4zMS;7kr9>V;PX@TN$jP6D%R^({V+Ss! zrM_sYOwDS}+MCxbhI1e6<@Xg1tQZ4p?lGsTm7?C`-LkOgaK< zWCw*k+l;+qVR|@J?ghqcZ~gq=@dZ}3Xr+;}Q^M5y$fNkml;k_M$O;AIEg#G6e@}}P zS(t({?j zv_f+9eUq{H6>M-GEb@ZL6% zplxojTZ~zCoqMB7oak!_!I+iYGzCXkhCs%Wx9uvnSDlg*`Pe=?OLEQ<1!Wsz)W~in z-wyps`3G)6`)8S1H1Rd6i`umJLfG{J49%F^;}9^h(g^;@w;jTeLJBgM_>o_=D1e`@ z%CC?ArMoz(vDC&Os-Ti z3WPkICnb<=J7tlw2_q%qmWip_kswV{Bt(~elA&*McWG{%06d@;J}?mO0cXcW^#Qsp z3q$!@=W;$O-wjfhQ%J4obL?_=a@Th+|J0oe8@oxrpb!s052>5SMBa8a2``XTmc7oO zz4OWziVc0wDAX8Q2$OJ{k*`o>RZKyfH~~1#o+|nm6DzghTm@ICYaM6s`HSI~G&P3M zDR0pc{F>$#>0gy1ILLM$>IL}Fp(k-2>A)`aNer&p>kHY3=ZT~~wDY##gpi{MuAft? zrSIG#KVI5nDmIuY` zlUj^sQHRo0jyNJU4rYCI>K5FxN&YZMeJ>feR~giDVhoqDk1(Y13Qi*}6=SyK=xl(E z)-8Pp6$mzw@*qNk7Sk1DU?eAMN5!&v2Ak#a)Kmsvh;Os^wQcWKdBvR<3$kabQ_|APS|iNV^h0DQuFojvtXe=}o4*IPY;Hbs z%#NCurXq_C=hmr2wD3Vnsbdcq_BSCilR!P#f`q~nA^P*Gd)VT#t5ya(0|yN~IMFJD z70AZ>090bSF$Idm^dk{k(%d=8Cd$Jr<(LzdeLJ4aB|b17!sjhBWOgqNtT^V&AapVj zBySDwMP>A-Tww+u=P_vqk|-1c{*0n&P!_+YN3fMcI_-D$FF;NHEzsnq)siZ|3SV6} z7(36PUuUT`DaOmgd#{G3@EJ;3+H9>(ob9uG{)O8a>O!ovI3bvrDe{V>Hr_j|(u$s9 zr-c2mk&JnT-kP?jQw%HHfYA(LAZa$&n9C>-oZ~R6!RzsfiywBHZ{nR=>aWFfV6FW# z3qhrb-H+ywxS3QNzL_B&E!1E#Gus}Mh}g~ zbCOeM%D{K!96EEj`z4!ZEFX%Vg_dZThwEv&S31ja%9ybfwhhmyISFd+m)JDbyXEEC z-6?^+1~FJu#xB~ud`D{$SX#3^I^oQuD((RmSTpJh9!KNLG!*_ zj_V?lSwj*w7y6@DD<{N880Prb%vHRN$x0gv@!e@^(%uD+?`0Y7x#fgh+-p?bj$SH^ zQcG+WwmPsZ5RdC}EZSLT)Sw(rBZsxdn2}>gl~zEO-uPIi$tzofZ9OtZ?|YJ4mkPrB zD8QTiPL?(rpa=UPlg72S=h7w!^jl!c~B_RPT| zTu@W1%~3n3vEp(5%B_TXlwh|u>us@vvP%+i?vT&inLi`d9M#pt3=|4rCW_pX$N86v zCWfOME>$$4gDH0{O$+1>!mrS9;C{x$UYDwvCKcfV{~KCSj^5&E6MgI{3$x$WH0}Rh zK}i^ul(XEWDt8ElE2>?!6G%G1mRn7kyO{mcr8x1^6B%6u*%6;&o5gLvmw}D>|D>@E zX25>x@9HBjI>VF9pf^w@x7vkHMUL$5@9!u5adyx|$Zb3gF4@eXHM4#V-|RC>%Tqf* z!QfrB>5+J)L(o_Xn#B2EC^@?jCq5xmK8%K(d9oHak|d|>9(!|JwJbflej8yFh1RXD z%tT7c@^Na8A10zOxom?D+X$h8)^luu5e5U`##=xr<{~&$sL>;>Rptbl8tt-9^V`5| zi``5uv(PFf&FQTPy$^&mHDaXq68{h72c!NEH`k1>0yFAVxc;UJ5q4`y$LheFKUXVZ z87Cf0F-Fj)4{SF`tZrX=QZ`R|!Jgtv8?z9On+xr6qa_(4r4SQF4*`+ZT~eryR9B)#qR%gQ(#6~QopDnz9<&!X2naCq3{c^&_ zEs2SIaMW?fXHOKn$Yue-2y5+MFpG|L#4ds@^cQK)*aYBiiBiDter(_I6A|h=s3jYqJ@bZLqrDHSMs?z)s7z#K*g~wUZ__O zbMH^uhm43GKT@nK<1C$JQ^bQAnUb1X$pSFV?`FUo#A||AC8RPXT#s26v3g(iB-5S1 z9}h7UjIP#lW}2Zuj-3BH`~|%9{LB7i(2B<3?{NV9;D=ayW72yTSt_V!zh14A7|A&C zpNOl#ZttV#?bog%J?d;~|DKwf$)iLIm7=8BR#9zagg-haqRw>-xzTZv?DrYMAA;!d z3udLvp{B8wOH182%DeYR=i^)cbz@Pinz&}#WtpIzf++>1;~h zwX)AdG>I++66k%8^40XuemQusXYOr(1ox7*#y(VL4o%X^TzO~RN8Kgpc7WSy_D=zZ zsZLB&7VY#RC_SaKJx&+*X)g^(|4$20m?7NL&xFqkDxAWyvf(~zACs#{Ws;W4nVw3m4YQ-WgoCFxeq9MvL|lGq}?3n}uOfWpG~j#!5aRTh4PR|R!Prg%H2 zpub%zWG1e7aVYUmT#n8U%KFocv$UVv0Th6UTYR<(q@0RAaKXYL*R~DQNV7=z zX_pe`+Vrn%P(5(5A_4*ig0!CXmmCO5tBKUSe5z}d)=$a?Q3`B^Jwxj?S#;;j_BN6y*xUg#5Q$j9BCr%}kynIZ>90D#Zdi zH6+pn`jAh_OA==~M91i1ox~w7K5?K&3VW{Em2MmE(aX7Oplu?=owjP}7C6Sd%!7 zCD!5RYm{RpbI8oP+UB-6pudpbRIB51*ZmPKqW$-Lw2VpVqK|+`lUY;wF{1rcZzD;5 zoCHduUzIge_`y;^xTzGB+La1w4zw!05iJ#aYH`s%ttS)U#it3iVG%=4Mg$dzgjbU< zid^W=aleP}kJLm2eq!6Cs6SW!^cU_Ab#~myLr9tiDgF5z`^xCokx<-(2TQu7>~4AR zDU6VzbAldMf|(nGlICz-v$*2brhX2}eIBo#=pPTPZ&d$`%=TR&TyNT{N5nK>Nv%ci zx6WN~oG(l&V?S;b4}iy3S6AgctWFs!ul&AFPdt?=Fz0eLQ~QxFGnn8Zh8JilHqsv= zCI-PY`lOzD;3mjZK-VcyF6$c`gxz5+x zVg)xz_==CiHud03qyx2ws0&s)?Yo`JPkw*o#a}c)s1B0L+XNgYxrG!bvCH=)3u~Pn zV~duL6Fe>AZLw#%^Nhq0U!g#rrFcckRy65k*_bT2{iViAcA6``L0{;lI;U5=Arsy89Sw?R59jw@nlc_DuiyJN91!7XVX z^ju*jEW6=I5U}8HS1-N+Z(T70=Q^RKXk4Ncx_Ia+;uBh5%4BILY6Av3NW$6(xT}rNckajz{vLMKbzQ*U4Y&t4F0kcLUg<% z9Y(&w2Wh*>$YdYxBw~3$k&0L}yWlCB_>IMxQ0HLL@ypiO2T;7$1iYO*zPxHNhLNy9 zKr{#@ZP}#iVQ1FTY?um>}Q{Y(Zhd}y(EdfRqpn9s{Y`#=cw3W_v+$HYEj_yF(P zgsJ=7(SIL$L_p&}h!kwT`xx+J9O$!ss26k=6MZ~rCy~#0g>zYU$+~E)i2uFQV0uE< zVNhLFp@$X)P?ORHtb$rgkBUBYz&K~KFVCt{Yux4JLNvD?ylhwK+wdyQ;Ol=J_fuYZ&G z35N%3Qjf{RqHoi53t5`O zq(~P;;J1gnFI4Vw2bwi(Q3oA|YZEQ9!$y0ElVA$JMI>Bc;xKdZ*#iv1cy}vcOgUpO z|C(-IT*De`{Y5s&{*>^e6eoWXINMiyiAs=#-)k1{ZDR+*z?s!rFN%`613|s~TSIpdYnuQIf z7G6SuY99biS7z*)3$OEV#{>}0_c$O#m39JV1<{q24I|o?15*6zw?}zpCOtEA z+d<5K`GFvp!(#Jl!{MVjKQ+vMKHJrXFHvlF!0I`y7pQT^1c92dQa)f}zes|$;}ixf zdgz1i&77mpF5&6vX@?Hn2KM{+*WEzl=Qd#;M8fiX1DQZ)5|9}L)#pwr%xfh%M=Xv6 zR?>Vtgepe4FR`~$R#q0H?n6CDqybj+xIT&hCA6^)Pv`Mh-`fC_UhG+NFv5r%t=+XY z=4zX#ctP@!9Qe0IGkBeamh})x8pc52#oYtp%a1nFVLLuAR>wTcz5dDi)K%qVYI%^^ z70vj>8~4cFx#55)J7UtPwr$zFrAR!=gv#??>MV=0lQI9(bTASM#KyO2aW?(WI3Vx( z`+|{S8`J1BG)!QwEk|{N6s7ywFE5bA0D?5e4MEP%GH@ghL&j(KzOiCn14*bJCo8}f z`sdnOCkQAhDk=h@bN}5s5HPa1xHu4wb{K7AXIE2GW9Q)TpWi=7yzqE^IGn$Bb8`cM zFU1nEm?Zz1h=cEu&*dOy`4|MI&=|&oFO*{#1nJa%do5SU1((%yap5qv`}NHUxR8TL zJM$Qj+F=4hRMgegf%MBLX2Wwgwg~zjM5chCKw@sY44-OzQ-}8TQ+Gp`DK3yz_UG^4 zs@ht9@0$v%NWe$juo9u7kqA74xCo;ds^l?nL6ZMh*PDk^{YHPk+g#ctLzxmnlE{!L z^OUJjC_~5=iA+Vvn3C9q$WTI5h73uG2xV58QYa-w6cUml!+C$czjMxYp7UH!|J0?u zxBa*XlZmVj*o&*Cl*#@NQ{rmTLE}Ye{^?6YB>=|0) z#PKU@@B3Rln?%O}&=vIF)vH%MpV|&|Ps9}(M%%|)s2kgtDX`-dn*^PvViYx5bc0ss z8_W}#nRfd7DEI@g2YzFJN){L#nM{`&ZIDuPJ> zL)3tk754ArAi*l5mV)|#C5C4Lr3}Y-Y;4q*f9pK`_#Ex%0UUWyUjBDW?Ls3yuVL>Z z=qO8NnJXqfD`=z+_D0DyKE-0Na|#Mht*x#ANs3RuI5;&}6S9xufxhCH2}GA?NuVbF z1sBD-%6~G?JO|XCt&O5bEG#_m!sUf&yl-LLVJ!%BAF+-n^lYKEVH16&$i3izNSbR# zenG)Z&~p|R7R(sfka>SUz3c8Kz&NaY3LZc(fO3%c;K6yY4yQnv1rx8e7nG0Fi!&p@ zAy(mAYAlWAVE8aY#;fH=CCh<)eG$oInz~1bn_O&H*`2rDokqgVa3GB5s(z6*52-M> zu#igII+N0Uv)KX{a11#q59O5SE-KM|Fz2z3JsV7)dR5oFe|hYzpC1ElM2E&&x`2Rc zKzzxW^XLDm%?}*5wl?*I9Fj6Ms!j>sujmW0il{z(MX+@5OWtS6wtb8~y z*NK2T#I1UHdVU7zcSTw%la*OH{Kwj}XDUmdM>E4}j+EGA*seD!C@MJA5>al>KOVm>P=Rq=rL43ts1?CZ8YN1U>j~)AjPX3ta z?LoB-+M32yz-9bcq@iQv*9c9|&S(HOsQu*&P}dx^y@0_?Q^5hL) zkZJ}PCUan=o?o7B2KfP$wA8k)2kUlfo8RWxVVXe@2GW(!aBA$uq=QNJdnCmn6ogH5 zPA_=bG2GpHpW!vO?<%NDuQJybzQqBEmY1PoSn68SaZGAUS__Nvqs`mTIb`kG+(KtT zZmSlluHK89X5}slipk)NPRI_88qGw_>lTpwQ-!eNoPL3mYtLrt+El+0_Rd zZ|-&9ENveQ@Xs+|KUGINj>4fdU)%Z*Or@2tTWTu^T4{;>fe!a&6kD z1PScgHP%(~dNU^w3E$?LVp;xz%~5u4VUt)ksq%q6xUOe@%(bZxFx~VY?@|AQ+iJ_) zCg);eX?bITS`nlRA}BZI-~7~QhY0%>SP~vz=iT}~^4bg;2{RjK$jlNc$;l`wWp?0z zLu1DFKiB^%m5R4b4%BgZBZK)usFfB_(pN)mcP|)O!ZDezc5@xElW}>S3V$A>aRvk; z06iQi?-lf-w?QVj7gt6}ShP@@uDc#$xN+;|%{;Tm!YH}Ge8FQW4hGN8!nlC__jdN} z{R7`J5An0JVfFz+n@^RYa8q!G&JLs>I@&ffAiEK>Qt*B;-q5tvfup1&fh@4A;|RuZKPYcS0ziu#SoTGEA&N8nzmmz{wg% z-!ikaQw0kSCM9FJ8Ac2I)Ty_*aUjUBdaEUjbJ1w-I4)|iYnH>ddJj%s=;}P!{{4qa zUhv@vx<)edCLhgh5J~6lSbO)k0}Tz#_3GRXjYUHo32FXDK!GR|;GcX1XskAL1*nm~ z*rR6gGoU@fUp$*E|Hw_jh(J0}3E&xEH1djyhpWqqi;IC$|LlOhKQMsH-#90Ui|&5`zEtjYw-f`BWNC+k zZWn<0dp?qEJGS`IvuYBULx5sFf?ART%H5tOP}#cs`V5-rWNXVGKbCZ;S-!F(0|-Js z3gL#ZF1X z_yOGNnN`9c0I~Nt9x&&X>A0}quUB?7aQ*$aQZ4xjVAsY^dR`#RA*A2vm^9bOXmNh11!*O<%?SjQ4th`~uEk237%_IKjcW%DcA! zdMS$E`dF@Qxu7=V3^|iMpD`lsrVQfKds1Tyk}X0~|1OGpY+&J$<7N1{u54w6F?QJa7TT6r9l1=5Iqo zAAyUi$jghC9vGj)d%WM=+}rBx3sz;!__nxa!vh&==ilQr%59Y!!LN55Ob@_X2Sjrb z^LHKs1H`8c6~CY1G;~NIoIXAI{mrc^zi|ccFLn8ca}U|@Q3`8nLMJ~wxp(j0<4No& zK1ENeehz$6CM=P%A|~Az-&iY>@kUaTyn+HKVQK37{3rVE=v>wpPT0E7-y}nI5v;f{ z;D)iLLcq_O`274IRA-F7>p>-c0VVIO-f>(L1QwMKs2CU>uzJq`GG|_MyP_k1@fBn= zLOe_$R(wg-FO?6cTE+G>L9X-`A4A8-VD&*5;`UmZSKHn1T}_>rZ~gP~g|tWu-IzR* z?Abgz(}s;3*(;d&(M&5ycEu-7SJ?Sl!wtuyxj|y$%+`kVgkg~-e9pBV+raxHZIK-&DayL%9+Gbr4CmAQG5h(jS4vvBU>QP~x zuQge*^6%p46lG=7*I4OgT)z1jH980iZyvKG%czX>?&f1i-}&{LZ`*2DG<1B`ClPwf z>~_^}Z#?GgtgMn1;$I)ppVsv5Ks{1#KBBB}=Q*10o*oO(s(X6Q_Mb@M+Oj3~(rZGl z7ZhCErlF2`#Ns;oEjKzUDypfe2~%NH%EAzyZEd}BO51kZejH3wUA)W3%wkB{P-&@i zp;5L=b?k4-P4%(QZ4scR}!SJ7Ytik9G1G`LjM2{`CD5C?B5ws?+Er{Q(x6o&H1rTp`Tu1;L-DW zRZrJ%)VSL;C6VbwgiN}^K6rPZ>iKt@#?&JkV_CajT|pd5I5o#9ro{*&=@XSEI54cj z{_t#;UQF6)b~e)q3k4C^N^98*d2>J81E^hLC%?~PB)zCP}|=D<1_#LmX|%8380y$Z;qLA`Nmm+$kNV%Pm=((GlJi!Aya7#fA?uc62Ykz(cPBSsAYi8sX*nQU4 z)@deUl+r%A4%N3^T?r;)`C(=uA!=cx*F}wKBf^(*b-5|f2=W27oZQbkf~Qkn-1RG? z!|{Ab+ZbsuE&ph&&1x*VRf6|SIQrV9T#c0<@S~<;iSSEs*-XsF46fhKXR^enK^R=+Q~cHMnh8}r3_#z8NBg!xAIrRltXOSFnCuBX zwsN>m?DwEf*>=gw5keaV&I|(`J1lK!Wkm!+!9OV&b!p@zssm@aDcGh&9%gxQmWM(t zlyfz+w=pKBv72}MR@yD8pLqX0t)O|9hzeQ67f*Ia4QY}Jg-(;zYjfIJwb|XQ7As>L zoxXPUY}wKNy{z(5XrJkA6}^pyMOK!U@Ee6lEI_>tO$CE-AMoeM%+A%4Fpk z=xdNHpt|6&Hs=6ju)b+1$h_B0i;k9lMS8+)Eoq$&RoQ*P>&Sh@dC3yqGG+KPL_i-u ze1OB6!7w2Hyix+t4YD(yK)aQeCTR4(d&d>q2t^bEXA3Xg`|jQI=_WpEDFLHKCm+?J zEBIP=$c;c`{_zG573}AGYXpY2j}mufSA9*!!LIq8iFMpPb{+z}^mVGi4jk*~IiEAX zaKsJNN;@f;IJTo3^=>x0qSE?mL}J%24&xR*VVu$3zEn2r`%G{*gnn*V-dF>GbB3p) zy9R-kRs}r~J8{PJ-blk04j_h?R;I#g-QM2EA805%hfI^W;HUon*1MUhM}_?-e4+e<*_#%^s59lEr|vvMSPwtZj6w`oa)>@xaY zBLG@cW&Hn6Sg*DNkZFJJli$xwTE^_D@V1hzwp|D=LAsS|!eCDU84JYl%Hn9|8X{0I zZg)5J0h%izApvs=DMT1}O+jzuhY;v2UDzO%s&-z1;sH<|KD4*M@&QbfK%3NV2?=Pr zlmX6R-^$R%R}SsB6sPXr^0ayA_yV!P;+`2$2sxS3hhJm~W!9Lc(c?%{9dj@(r{6)# zi}cTwcbH}VhGkR;Oqh^Q)pYmtbfE^SW+0s;(}bk$g7FpIuY;?Tg8|CJ)Bxbg5pJ2T z%}))kyu`h)zOX~e+S=uBDO^&qk4W9v{$;kg4o zR|nor9YmtfFi~v!+`%;26R;s4pL)4c6U4B0A9Mhq4SjejO-~s)G9vnD+~NQv;BOdH zBr~!`MWa@+G{j6^jr~jF(Ufs-Y^Ugw_?ZkxEkwlvdipob-wFm%?r?LmH#3eI4EZ(R zW{8{d{Pc{7qxp~>0jLqQZW!So8E?4%Vc2Vs8hE<~XF@GaPTpKY|12N(?_lXIfOiGi zw!A!91LRTZojY-*6ozc0`ob3kq$V9xgSE8~x%P(^IdgMzB1_1f5q&(i=U+ZZ{fz@; zlP;|WjR?M>3VA3{$r;?%+vHEOQ6{iwRFbhVGD80L?}H`0>0TT-Ui1`f$N2^hQ&e!8 z`XDRamRko~xpTC?!D_MR-Jl`0@LYe^{(5M4+wA&8{{R8`<@5h0pBft(K@`;hHO#ba z^YbBV5=mgzP*=+$I9}$vZ>0e6qezDX)tNc>Z-r3U@1NG_-8+s6`i-2!o+e@kss65` za}E(est&05wQm}$l-1=$FjKEMFA~B9_|G`VE&vmcO1|&wTlh2;nqq$wixIy3GiDJh zYnjMQ!Hel=Y0a6+G{IawKlM(csc(aH^%vjD@9npDS-CUv-1*cX$+~c$DA!p@D#aaA zgj$MhY)mWp?1y;I_9Wyoj{#jPZ&!_sxAvV*j1p!^G{sIfWa+F=^*MNCd|tf&Bdp&` zfrgg?>Ec4umNtP_!KP-J4$)uVZdwWmomBWI@W7Ti+j2PFpmaX--bDxKbTdco~SQqh) zu~;HfmfRYls?xW85EnrdzRfq5lfVzzQrdVul-hqORf zSgQl@)}@ZH)iVm2vg<2kNhE~}=WkmU@_M@~ZQZZ9e5TSa<(PlJ-j-t`VQydQB9KlS z@^Ro=nxE=8-5f(k@C4u^(8~$6XYug_Z9PWI>4s^9$CoqPVNwP8eaZ?LSEU*1ez?curP_;&DS8Yw)v8avlFN3?D{P;AsJk$x# z1W|zna_2Js21-}$7z46hA{l9cGeeE;-9*5s?)THe{?PU`6S&Tun`40zT_GY}#B^oLgq6r;is1<#5Y|u9 zo8xD`#H_mEjrCaKR)ynW7*~5+8X8_v-?hVA*r}bkJ?hUPw^sV7mL08MpuehN+s`}~ zE@w;&Bax~SjgFa*o}T8}VI=cqUCs1Wui+pr&1+q*tq(OU_Un06&~Zk@@sPfV^z8GR zb@AI=!qs^#=fLZ%&Mz8bX&Fxs<{LnY-_clG?4Xunh=etCw^qn@CtlsPeRJ%{NIYkF z`u0U1Q@=3no_;Z<4>-ueQOfP4KU%b)l~-6zQ%pVsUxp(|b!3rq(h`N6uIh`bvzQy| zJQ#^l6|W~V^L*mip6^m0&H)JO`*ih?p!z75O8X72Z+m(mkHwT8&j=Whz-PS~lZ?yZJtEgEZEdTNZ%?rgYQ3O*8`~1cUXWE(?l2N-bShn2 zomvb1p5}^duKTH~K*wzh?{>tv$aG6idkOvql&;fPp^YxwF2U_(W}nE$VUB2-k15cE zuQSv>-kh2VP$k}<;p&?vg9bL9HH+VIhOC7}J;K7=HOg5l=T4UFi%c@nq>$T}M`_(B zt8ARiU1G|aR;811v8{h~_SOMZElLp9xYxXLa?GNi7_Td=Ao$lGR2K^$JaG5&+P3$U#eoBF zAaV%ctaD2|xc1l^gzZmCGF!{QeFtO;pcwulFI`cQsu*-YgdJN2`D5X6ZDZqC*S78x zJ8<(XwiUSKD057}<&KV{@P;lu|CPgXzb<9M+~5KOejzGgJ&~8UtO0^M_@vkV-_Yf4 zvQ1GNw}m!x9my-SJac9ipJ!xbWMgal)N9b*!a^Qq2V!xXdT_D=fyjv`rcvAXYY3p}SPRI~-$#o5g1K>Yu+Mx^x?@aVVjq59)~S0N9Ka~czKa~_4zYq-gGo1WQCyT0>^F^WTz_N z05JnLJJNf9FP3$22aNNpg1p`s;HK-oCynsCa020hq=cZh!2s4Va zvtLfl&;NMaJmL#|3eoI=L6mTRf>nU94wks0S4ZS4CB+2UD;qD>*sV~0~|oz zNgxiIfG9)Y(LW;AKt&JIC?j;~$9YJwp8Ufze(*E4HNk-hVvC_;2y}9OZU$a-y>|EJ z*xhy&-k2^9F_U$Skwi^lbbNd~Gw5f&kWQ2x(&lg_0m$*&+S)!TDJXtRlNzmRZHCye zUtygyOH9kBEhc*$y^YCd>Aa=VZl*5Ymbt+~R!S8yVA7b9TjC56LA3iPA7gSOIobd0 z+0Od4rINL;ga05bnP_QN+fmVgGt5vaN}TvNo4Gdd?%fR&F+M&%9tuLE?8{!*iEy>u zU0qtjzL1?z;Bc0>1J0+=*$ufv*VKnN=f@CI{r_d#5n*!j*_XMnto_8o!mDuSpryCA zvNCY`Yvb||0$72dF`*@m1cT*;W)1ra?;!|#xFMtQWD_yV_c2!Akahj@u(~>3#>&CL z)S-dY&ex z`>9jdD@TfK^fp39cMMuvSp2CPT=^Bn=Xuh@BN!oLpqK!n{r4-%(;*0XIUR!V?uKF2Zf6*qmXnKc5jpk` z`<0!pm_I@)69N&n;+tph8oKX2)ssgQ;$}uQRu{fq+OWkp^RY1J`R}a0DX;YCQc(kW zcxL71c75v6r=G{K7jRZ2c^7%&N?v4Tkd@cba)+ z1k0&H`x6ok7(H7Xn~dO@)*YF_*FRI5Z2s3$(>uIiQSR%{P0(S-sgr}XVcmBP|Ck^J zEW*y|t`RsRVp>6DkdgHmF+UZb;=O4C?avS1a&rGwy{S{;i8Px9W8g;G*P67lYZHwF zm1lrsm#`7+>y_!PJTtnAwD-5ff2e<9)&D_uL&VE?E?%Y_dI$^ofI}B!&0}ZTz~Pnz zi{oFA?kt7@9%;KVyxY^uYgBCm&M>gYHo{qi{w>R#9H>MteNf)Jh@0ZXItVY~`Fzf5 z@w^MFB;G^XZIaGZOACw6RX9VD4SOktz@Fpy1xi+`t@F;Zi>v>pU?WG7ui!>J0F~94 z;txCdUkPkZrM`Yq@UlUh>Vva9&e~tIR*coF+Vz63sJ78}{EW;XkvLh+UVqQ#zHsRH zuYpQbgjbOkaDbYiG+#sRLayn3k!|n5KrmvR_Eec~i0rbf>tH5N{^-7k)ef729pOqv zW=CP~UO#{T?s^nRk%9g2!N$_EksFB_PW}*>39#Ue(@w#PBWUz;cmG)Cx{(6XtQ4|o z>{r^_jDq@L#w=xV;>GYQ5%>~9_^C&KcEu|Ecj$EE7Gq=M(KokBDXDnA$BE>tRO$r@^6>J0~{VfQ?~z(Zy@K33a6V65gRKI;Mj+M zkQ&om!}*AGC7%mUuY+gNw$;^j6nh_Q%oC*+=+q^Yp86iUC8F;;^)xmx_N;}(U8Ez7 zlO?UI1E#SoP(J^I(@NMR=BPX)%Q5)q#4CZo;Y~|F6L*;6+l-fg^vv;UJ!z@+`OCg7 zel%OjjK^dr%z`85`OSm5o=9tIMX;|5a!FmR>3+H4y6fYT1YSii^_Jt!%uMbK4TJ!2H}N{oFy`+I8X2C0fKSO#BwI+4sE@?ND! zsR9R4H^G1oV7Rc&i3A@)h3Ds{J`*V#Re`~(bJEX=QVFC|QAtVecK?u~6^%|5g+e(Vn*LE-ol9BMsBpvE)iD7Dn^jJ}k>l2Y_cQkHRCU5 zQbUtynnD4!`J233YgXzkr}JtWL-v*AuCA^Z+}lkVNPl%h|0R7?kY&~z*JM{sCfy;E z?v#|AKX8C$O=*cT3N*}p+)we@CL0IiXI>SLfP$4*eqDXg7f#}~+@*SQ))gAF2&UAs z!s`*je2dJSWj}tq@#^*DsH=!SzV-D)%CWy7sL!O-E}tO28(m%eMpAC%10@Hv;*xPO zc`0F4DIqV)f@72?blj*3dndk~#GZf)_7jW~zN%tW6W!EE+=F%sBniy^lgkE5$!l+d^*uScsV zqdhr!yGf+*UG(QT&dM?b~QyFS6M2Xhv8tZk)wQ#p>nr z3i0#2lN9-vwl4IMT62$fpFGY$YVk7L&y$C`w+F75EBwx7{%~;n;~bMoIVKY6FOzbZ z;BMR{;*^ij)Kn8O6$_obl=vlvk)*_+v$TBANRug$5!tj$*Gh!~V~*;`qn~C(fNBOV z{cNUbB$Lc^&P&DmA9=jashbp@?r6}yt)niYWcw6xtt9V`8f{Sr*VR;clHgPB>$8^| zUQ{#TsSmH4L*vgFgbvfURmF-7#5pB)?{3_>j^xPBNok+SWn{kN9qx2+V=)uSB)hPX zY&l6Hy-y73WfyCqW4vTW!Z}4*dGSGiAIiF!gu5_F0d)v;r} zYav8xOcxOUegQNzE&Y9){EX#8$4DglwW1bvPufO1GsM{dC?9^Ir_MgoLE4cm5a;~k zdv8o^tWf^s6+DYmWYiCy@RtpFq%*1lJ7&2X2o9d-s6&y5bNS` zy=tCtRZU5WlzXC)`)aaMH!$G{?<3*Uy()Uzcj86Ft`6GSCj5N(_1C&^olQH|m(|91 zkt;|<0B+Zl*rAkn%2iQ(-BqIw(lAZ7UAuZ6e_S)pH2g@R5iY+u*EtqrMlVhi+c_`GB9Klh~Z)V28fDieroioLwb>Nm@iE^|K zU3k$RE16ivjnw!J#F1nV77sp!{i0fI-8`gyj@L!$wglY4+6@2HmAY@;M}1H8H2%8V zT;cpqSxtI8-?bUh==SoRuJYp+nhZGYIv{zWN|N-R;wb7O{y+cen&b3Q(6Rj0`k1{x zyi*wJf*Qni6nX`HSDE?o+xX)e%J1|ITX#Ff-+7fKdN)QzM}^K&G>Ti!%%ZpsLnpXV zh^6Rr32Sf7jTYlmZ1L@+)T`!`#__X9zMOqE$4nwon~5^9=h6XF(VIA=MCMC=vEkN9 zucdXQJ(~eFww0P)6mA*0z<`@=%o_~9W(_=y(Du8Zov}6gmJF%M$simo)^EbQkFrr1 zvmS~|N#VFRUb1>&bL9EmBxRvnq_R|TT4o$@#@lyCvBcaLOmdQNqT~JR*U}$P4JDC; z7*k_oW9N3`4jbC@EN$E3B1CtDk@O|gfcc$IJ!3HM=6H#{@k3%P;r8U#cO17p;!Q4* zF$-VTwDc5)H`^}Azt(RYBx8tK$o<+@Jb$_A)yA zqB#sH{-qb0J(F=U%LGJ zNzSH3Zz(qSgfy(fR1>!gu z?tWv3FI3~cg_KL3)^Eac``1r9t)3Jw|r!Qj)7sZ(ILb5Ki?!CMrsW`u7SL57E70WHAeW zo1i2^R`$F4lXD&6AW521TJs#S=5FD%MI^L9+r_t6<`PM+lU5}a9j4d&X(Uvn2hMXW z$dj4yW}O6+I9vnPVGPK&OCEkjD|o?%2VJ$%SDjLd7Cqn372+D>{6DmyP!;Hyd^Vzp zE4wKSSwXzw6k3ny0GlZ7JKjnj7S`lVKh2rLh0)Z6G{F$e`#!Q}M_oC$Er$;6GER9C zw`KPUm+0>=*IU}=Z%Z+JH&5TqGralmi0r;|h3k~*dkBx!$9c+TSBejLUg2##(}K`V zE!IsR9r!Z(1X*7t{AKR`Z&>#Ib~}2-shTH<_Gp}V3QKLDo(AJVuPg@VRT|R7K|wYH zvBXyALmtQY$c$)W`dJU+EsJWf^{-bxs-qzjjAX}(p?4eqQccu2^ z=<3R{USy`_CBD28x1kbqcE5)Rg$s8Z)bdrH@(I|ujAqm+o!8PQri`DwxJi$Nsmnuw z(=WMdZ5`fyCYNTuYWaC)iWc4E0*+izdZ^Q_-vSb}MZ&N*fvQfrOxS3`X8R_cN# z(4Jm$ikFzc38&uBiOSaIqt2R6n<@Ca#Fg9V$?K7KF0=Gz>AkSV_GLBG(eIgC7 zKkLBxi8eaOCQ{_kI%Qf5vpsLWup1S z!#7$UC7ou4x7tm1T@s>k5ptBoz=c=qm7dtj@Ko@ViRcZB3n!(-C8aLCNYG6&S7!R3 zFVSI6RXm)}l&3h$c8ejq(Pqdo#3_^Cr!Hzt_PMTdEgg4`0hiw9 zQcK-5$aLs}Mh-?_o>ZdvO1isW_?xaY&DYyeiEZ%(hcXW`G~5+E$)6a_p4M+D8XH~z z0dv}MgMN{Qc=bu`(sP1Lwf8pH-FVyoHv`E>ImZ`_m3A(BU$E=K+-*Bo!$ zdfPIrgBXQ>(cE34asThp>&;SR{ZGa)%4+XV`rs6|_e@2tHq%#2Mix3r5Ti*XvVM|v zzHn#t2mR34TQ5c4ChZI1oFI{l9%>!@@BJJ?bA?^3v5ROFa^c#WOJYgNcTm>9}DqtB)69Ce=pj(-kNoUw~H@YU$l8uS5ABusfGJg<;!in zBOjU#96~%TU7VUI>LUDj_MX9 z=?Wjty}ZtDo3fU@lJ(ZoM?&=pwAavg0a4TLc->vf{o+3?zI@KTpwDrMIbV!^4;Lx@ zzwsb3Q5?HA8q)Vz_;T6bxVU&#HVdbwbYr#93l38xXliyYWY3wG@0MEf&QT+rMH2S1 z3ON>&{$~Y+YYW|&uBU#MVx<*hCjI9s@NF)eH|jj1SE2!V8-K!! s{(BnyOY?sQ1pn&!Z>{{VKU%0w54bXLs%KpZeoivfJE&W%?Req;0<}^_bpQYW literal 0 HcmV?d00001 diff --git a/src/http_server.rs b/src/http_server.rs new file mode 100644 index 0000000..3ea23c6 --- /dev/null +++ b/src/http_server.rs @@ -0,0 +1,590 @@ +use http_rrs::ThreadPool; +use log::{debug, info, warn}; +use openssl::ssl::{ + NameType, SniError, SslAcceptor, SslAlert, SslContext, SslFiletype, SslMethod, SslRef, + SslStream, +}; +use std::time::Duration; +use std::{ + io::{Read, Write}, + net::{IpAddr, Shutdown, TcpListener, TcpStream}, + sync::Arc, + thread, +}; + +#[derive(Clone)] +pub struct SslCert { + pub cert_file: String, + pub key_file: String, + pub ctx_index: u8, + pub ctx: Option, +} + +impl SslCert { + pub fn generate_ctx(cert_file: &str, key_file: &str) -> Option { + let mut ctx = match SslContext::builder(SslMethod::tls()) { + Ok(i) => i, + Err(_) => return None, + }; + match ctx.set_private_key_file(&key_file, SslFiletype::PEM) { + Ok(i) => i, + Err(_) => return None, + }; + match ctx.set_certificate_file(&cert_file, SslFiletype::PEM) { + Ok(i) => i, + Err(_) => return None, + }; + match ctx.check_private_key() { + Ok(i) => i, + Err(_) => return None, + }; + Some(ctx.build()) + } + + pub fn new(cert_file: &str, key_file: &str) -> Option { + let ctx = match Self::generate_ctx(cert_file, key_file) { + Some(i) => Some(i), + None => { + return None; + } + }; + + Some(SslCert { + ctx: ctx, + cert_file: cert_file.to_string(), + key_file: key_file.to_string(), + ctx_index: 0, + }) + } + + pub fn get_ctx(&mut self) -> Option<&SslContext> { + // self.ctx_index += 1; + // if self.ctx_index > 5 { + // self.ctx_index = 0; + // self.ctx = Self::generate_ctx(&self.cert_file, &self.key_file); + // } + self.ctx.as_ref() + } +} + +#[derive(Clone)] +pub struct Site { + pub domain: String, + pub host: String, + pub ssl: Option, +} + +impl Site { + fn connect(self) -> Result { + match TcpStream::connect(self.host) { + Ok(i) => Ok(i), + Err(_) => Err("server not canacting".to_string()), + } + } +} + +#[derive(Clone)] +pub struct SiteServer { + host: String, + sites: Arc>, +} + +fn split_once<'a>(in_string: &'a str, separator: &str) -> Result<(&'a str, &'a str), String> { + let mut splitter = in_string.splitn(2, &separator); + let first = match splitter.next() { + Some(i) => i, + None => return Err("first split nat foined".to_string()), + }; + let second = match splitter.next() { + Some(i) => i, + None => return Err("escond split nat foined".to_string()), + }; + Ok((first, second)) +} + +fn get_site(sites: &Vec, domain: &str) -> Option { + for i in sites.iter() { + if i.domain == domain { + return Some(i.clone()); + } + } + return None; +} + +impl SiteServer { + pub fn new<'a>(host: String, sites: Arc>) -> Self { + SiteServer { host, sites } + } + + fn get_site(self, domain: &str) -> Option { + return get_site(&self.sites, domain); + } +} + +impl SiteServer { + pub fn start_http(self) { + thread::spawn(move || { + self.run_http(); + }); + } + + pub fn run_http(self) { + let listener: TcpListener = match TcpListener::bind(&self.host) { + Ok(i) => i, + Err(_) => { + info!("Http server listener bind error"); + return; + } + }; + let pool = ThreadPool::new(10); + + info!("HTTP server runned on {}", &self.host); + + for stream in listener.incoming() { + let local_self = self.clone(); + + pool.execute(move || { + let mut stream = match stream { + Ok(i) => i, + Err(e) => { + warn!("{}", e); + return; + } + }; + + match stream.set_write_timeout(Some(Duration::from_secs(10))) { + Ok(i) => i, + Err(_) => { + return; + } + }; + match stream.set_read_timeout(Some(Duration::from_secs(10))) { + Ok(i) => i, + Err(_) => { + return; + } + }; + + let addr = stream.peer_addr(); + + match local_self.accept_http( + &mut stream, + match addr { + Ok(v) => v, + Err(_) => { + return; + } + }, + ) { + Ok(v) => { + let (addr, method, host, page) = v; + info!("{} > {} http://{}{}", addr, method, host, page); + } + Err(_) => {} + } + }); + } + } + + pub fn run_ssl(self) { + let listener: TcpListener = match TcpListener::bind(&self.host) { + Ok(i) => i, + Err(_) => { + info!("Ssl server listener bind error"); + return; + } + }; + + let mut cert = match SslAcceptor::mozilla_intermediate(SslMethod::tls()) { + Ok(v) => v, + Err(_) => { + info!("Ssl acceptor create error"); + return; + } + }; + + let sites = self.clone().sites.clone(); + + cert.set_servername_callback(Box::new( + move |_ssl: &mut SslRef, _alert: &mut SslAlert| -> Result<(), SniError> { + debug!("hangs"); + let servname = match _ssl.servername(NameType::HOST_NAME) { + Some(i) => i, + None => return Err(SniError::NOACK), + }; + let cert = match get_site(&sites, servname) { + Some(i) => i, + None => return Err(SniError::NOACK), + }; + match _ssl.set_ssl_context( + match match cert.ssl { + Some(i) => i, + None => return Err(SniError::NOACK), + } + .get_ctx() + { + Some(k) => k, + None => return Err(SniError::NOACK), + }, + ) { + Ok(i) => i, + Err(_) => return Err(SniError::NOACK), + }; + return Ok(()); + }, + )); + + let cert = cert.build(); + + let pool = ThreadPool::new(10); + + info!("HTTPS server runned on {}", &self.host); + + for stream in listener.incoming() { + let local_self = self.clone(); + let local_cert = cert.clone(); + + pool.execute(move || { + let stream = match stream { + Ok(i) => { + debug!("norm esy"); + i + } + Err(_) => { + return; + } + }; + + match stream.set_write_timeout(Some(Duration::from_secs(10))) { + Ok(i) => i, + Err(_) => { + return; + } + }; + match stream.set_read_timeout(Some(Duration::from_secs(10))) { + Ok(i) => i, + Err(_) => { + return; + } + }; + + let addr = stream.peer_addr(); + + let mut stream = match local_cert.accept(stream) { + Ok(st) => { + debug!("ssl esy"); + st + } + Err(_) => { + return; + } + }; + + match local_self.accept_ssl( + &mut stream, + match addr { + Ok(v) => v, + Err(_) => { + return; + } + }, + ) { + Ok(v) => { + let (addr, method, host, page) = v; + info!("{} > {} https://{}{}", addr, method, host, page); + } + Err(_) => {} + } + }); + } + } + + pub fn accept_http( + self, + stream: &mut TcpStream, + peer_addr: std::net::SocketAddr, + ) -> Result<(String, String, String, String), ()> { + let octets = match peer_addr.ip() { + IpAddr::V4(ip) => ip.octets(), + _ => [127, 0, 0, 1], + }; + + let dot: String = String::from("."); + let ip_str = String::from( + octets[0].to_string() + + &dot + + &octets[1].to_string() + + &dot + + &octets[2].to_string() + + &dot + + &octets[3].to_string(), + ); + + println!("{}", &ip_str); + + let addition: String = ip_str.clone() + ":" + peer_addr.port().to_string().as_str() + "\n"; + + let mut reqst_data: Vec = vec![0; 4096]; + + match stream.read(&mut reqst_data) { + Ok(i) => i, + Err(_) => return Err(()), + }; + + let reqst = match String::from_utf8(reqst_data) { + Ok(v) => v, + Err(_) => { + return Err(()); + } + }; + let reqst = reqst.trim_matches(char::from(0)); + + let (head, body) = match split_once(&reqst, "\r\n\r\n") { + Ok(i) => i, + Err(_) => return Err(()), + }; + + let mut head_lines = head.split("\r\n"); + + let status = match head_lines.next() { + Some(i) => i, + None => return Err(()), + }; + let status: Vec<&str> = status.split(" ").collect(); + + let mut host: &str = "honk"; + let mut content_length: usize = 0; + + for l in head_lines { + let (key, value) = match split_once(&l, ": ") { + Ok(i) => i, + Err(_) => return Err(()), + }; + let key = key.to_lowercase().replace("-", "_"); + + if key == "host" { + host = &value; + } + if key == "content_length" { + content_length = match value.parse() { + Ok(i) => i, + Err(_) => { + return Err(()); + } + }; + } + } + + let site = self.get_site(host); + + if site.is_none() { + return Err(()); + } + + let site = match site { + Some(i) => i, + None => return Err(()), + }; + let mut site_stream = match site.connect() { + Ok(i) => i, + Err(_) => return Err(()), + }; + + match site_stream.write((addition + reqst).as_bytes()) { + Ok(i) => i, + Err(_) => { + return Err(()); + } + }; + + let body_len = body.len(); + if body_len < content_length { + let mut body_data: Vec = vec![0; content_length - body_len]; + match stream.read_exact(&mut body_data) { + Ok(i) => i, + Err(_) => return Err(()), + }; + match site_stream.write_all(&body_data) { + Ok(i) => i, + Err(_) => return Err(()), + }; + } + + loop { + let mut buf: Vec = Vec::new(); + match site_stream.read_to_end(&mut buf) { + Ok(i) => i, + Err(_) => return Err(()), + }; + if buf.is_empty() { + break; + } + match stream.write_all(&buf) { + Ok(i) => i, + Err(_) => return Err(()), + }; + } + + let method = status[0]; + let page = status[1]; + + match site_stream.shutdown(Shutdown::Both) { + Ok(i) => i, + Err(_) => { + return Err(()); + } + }; + + Ok(( + ip_str.clone(), + method.to_string().clone(), + host.to_string().clone(), + page.to_string().clone(), + )) + } + + pub fn accept_ssl( + self, + stream: &mut SslStream, + peer_addr: std::net::SocketAddr, + ) -> Result<(String, String, String, String), ()> { + let octets = match peer_addr.ip() { + IpAddr::V4(ip) => ip.octets(), + _ => [127, 0, 0, 1], + }; + + let dot: String = String::from("."); + let ip_str = String::from( + octets[0].to_string() + + &dot + + &octets[1].to_string() + + &dot + + &octets[2].to_string() + + &dot + + &octets[3].to_string(), + ); + + let addition: String = ip_str.clone() + ":" + peer_addr.port().to_string().as_str() + "\n"; + + let mut reqst_data: Vec = vec![0; 4096]; + + match stream.read(&mut reqst_data) { + Ok(i) => i, + Err(_) => return Err(()), + }; + + let reqst = match String::from_utf8(reqst_data) { + Ok(v) => v, + Err(_) => { + return Err(()); + } + }; + let reqst = reqst.trim_matches(char::from(0)); + + let (head, body) = match split_once(&reqst, "\r\n\r\n") { + Ok(i) => i, + Err(_) => return Err(()), + }; + + let mut head_lines = head.split("\r\n"); + + let status = match head_lines.next() { + Some(i) => i, + None => return Err(()), + }; + let status: Vec<&str> = status.split(" ").collect(); + + let mut host: &str = "honk"; + let mut content_length: usize = 0; + + for l in head_lines { + let (key, value) = match split_once(&l, ": ") { + Ok(i) => i, + Err(_) => return Err(()), + }; + let key = key.to_lowercase().replace("-", "_"); + + if key == "host" { + host = &value; + } + if key == "content_length" { + content_length = match value.parse() { + Ok(i) => i, + Err(_) => { + return Err(()); + } + }; + } + } + + let site = self.get_site(host); + + if site.is_none() { + return Err(()); + } + + let site = match site { + Some(i) => i, + None => return Err(()), + }; + let mut site_stream = match site.connect() { + Ok(i) => i, + Err(_) => return Err(()), + }; + + match site_stream.write((addition + reqst).as_bytes()) { + Ok(i) => i, + Err(_) => { + return Err(()); + } + }; + + let body_len = body.len(); + if body_len < content_length { + let mut body_data: Vec = vec![0; content_length - body_len]; + match stream.read_exact(&mut body_data) { + Ok(i) => i, + Err(_) => return Err(()), + }; + match site_stream.write_all(&body_data) { + Ok(i) => i, + Err(_) => return Err(()), + }; + } + + loop { + let mut buf: Vec = Vec::new(); + match site_stream.read_to_end(&mut buf) { + Ok(i) => i, + Err(_) => return Err(()), + }; + if buf.is_empty() { + break; + } + match stream.write_all(&buf) { + Ok(i) => i, + Err(e) => { + info!("{}", e); + return Err(()); + } + }; + } + + let method = status[0]; + let page = status[1]; + + match site_stream.shutdown(Shutdown::Both) { + Ok(i) => i, + Err(_) => { + return Err(()); + } + }; + + Ok(( + ip_str.clone(), + method.to_string().clone(), + host.to_string().clone(), + page.to_string().clone(), + )) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0c06c61 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,71 @@ +use std::{ + sync::{mpsc, Arc, Mutex}, + thread, +}; + +type Job = Box; + +pub struct ThreadPool { + workers: Vec, + sender: mpsc::Sender, +} + +pub enum PoolCreationError { + InvalidSize, +} + +impl ThreadPool { + pub fn new(size: usize) -> ThreadPool { + assert!(size > 0); + + let (sender, receiver) = mpsc::channel(); + let receiver = Arc::new(Mutex::new(receiver)); + + let mut workers = Vec::with_capacity(size); + + for _ in 0..size { + workers.push(Worker::new(Arc::clone(&receiver))); + } + + ThreadPool { workers, sender } + } + + pub fn build(size: usize) -> Result { + if size <= 0 { + Err(PoolCreationError::InvalidSize) + } else { + Ok(Self::new(size)) + } + } + + pub fn join(self) { + for ele in self.workers.into_iter() { + ele.thread.join().unwrap(); + } + } + + pub fn execute(&self, f: F) + where + F: FnOnce() + Send + 'static, + { + let job = Box::new(f); + + self.sender.send(job).unwrap(); + } +} + +struct Worker { + thread: thread::JoinHandle<()>, +} + +impl Worker { + fn new(receiver: Arc>>) -> Worker { + let thread = thread::spawn(move || { + while let Ok(job) = receiver.lock().unwrap().recv() { + job(); + } + }); + + Worker { thread } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..810b793 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,116 @@ +pub mod http_server; +extern crate yaml_rust; + +use http_server::*; +use std::sync::Arc; +use std::{fs, thread}; +use yaml_rust::YamlLoader; + +use log::LevelFilter; +use log4rs::append::console::ConsoleAppender; +use log4rs::append::file::FileAppender; +use log4rs::config::{Appender, Config, Root}; +use log4rs::encode::pattern::PatternEncoder; + +struct AppConfig { + sites: Vec, + http_host: String, + https_host: String, +} + +impl AppConfig { + fn parse(filename: &str) -> Option { + let Ok(file_content) = fs::read_to_string(filename) else { + return None; + }; + let Ok(docs) = YamlLoader::load_from_str(file_content.as_str()) else { + return None; + }; + let doc = docs.get(0)?; + + let http_host = doc["http_host"].as_str()?.to_string(); + let https_host = doc["https_host"].as_str()?.to_string(); + + let mut sites: Vec = Vec::new(); + + let sites_yaml = doc["sites"].as_vec()?; + + for s in sites_yaml { + let mut cert: Option = None; + + if !s["ssl_cert"].is_badvalue() && !s["ssl_cert"].is_null() { + cert = Some( + SslCert::new( + s["ssl_cert"].as_str().unwrap(), + s["ssl_key"].as_str().unwrap(), + ) + .unwrap(), + ); + } + + let site = Site { + domain: s["domain"].as_str().unwrap().to_string(), + host: s["host"].as_str().unwrap().to_string(), + ssl: cert, + }; + + sites.push(site); + } + + Some(AppConfig { + sites, + http_host, + https_host, + }) + } +} + +fn main() { + log4rs::init_config( + Config::builder() + .appender( + Appender::builder().build( + "logfile", + Box::new( + FileAppender::builder() + .encoder(Box::new(PatternEncoder::new( + "{d(%Y-%m-%d %H:%M:%S)} | {l} - {m}\n", + ))) + .build("latest.log") + .unwrap(), + ), + ), + ) + .appender( + Appender::builder().build( + "stdout", + Box::new( + ConsoleAppender::builder() + .encoder(Box::new(PatternEncoder::new( + "{d(%Y-%m-%d %H:%M:%S)} | {l} - {m}\n", + ))) + .build(), + ), + ), + ) + .build( + Root::builder() + .appender("logfile") + .appender("stdout") + .build(LevelFilter::Debug), + ) + .unwrap(), + ) + .unwrap(); + + let config = AppConfig::parse("conf.yml").unwrap(); + let sites_arc = Arc::new(config.sites); + + let sites = sites_arc.clone(); + thread::spawn(move || { + SiteServer::new(config.http_host.to_string(), sites).run_http(); + }); + + let sites = sites_arc.clone(); + SiteServer::new(config.https_host.to_string(), sites).run_ssl(); +}