From 06d980666b5a1eaf29f8c9b27852a7c442c8b89d Mon Sep 17 00:00:00 2001 From: JustinAJ Date: Mon, 21 Dec 2015 03:04:59 -0500 Subject: [PATCH] HTTP::Server is now minimally functional. --- Jupiter/HTTP_Server.cpp | 190 +++++++++++++++++++--------------------- Jupiter/HTTP_Server.h | 11 +++ Release/Jupiter.lib | Bin 302612 -> 302612 bytes 3 files changed, 102 insertions(+), 99 deletions(-) diff --git a/Jupiter/HTTP_Server.cpp b/Jupiter/HTTP_Server.cpp index 6bff1a8..3143f6a 100644 --- a/Jupiter/HTTP_Server.cpp +++ b/Jupiter/HTTP_Server.cpp @@ -66,7 +66,7 @@ Jupiter::ReadableString *Jupiter::HTTP::Server::Content::execute(const Jupiter:: Jupiter::HTTP::Server::Directory::Directory(const Jupiter::ReadableString &in_name) : name(in_name) { - name_checksum = Jupiter::HTTP::Server::Directory::name.calcChecksumi(); + name_checksum = Jupiter::HTTP::Server::Directory::name.calcChecksum(); } Jupiter::HTTP::Server::Directory::~Directory() @@ -76,19 +76,24 @@ Jupiter::HTTP::Server::Directory::~Directory() } // host/dir/content -// .hook(host, "dir/content") +// .hook("dir/subdir/", content) void Jupiter::HTTP::Server::Directory::hook(const Jupiter::ReadableString &in_name, Content *in_content) { Jupiter::ReferenceString in_name_ref = in_name; in_name_ref.shiftRight(in_name_ref.span('/')); - size_t index = in_name_ref.find('/'); - if (index == Jupiter::INVALID_INDEX) // Hook content + if (in_name_ref.isEmpty()) // Hook content Jupiter::HTTP::Server::Directory::content.add(in_content); else { - Jupiter::ReferenceString dir_name(in_name_ref.ptr(), index); + size_t index = in_name_ref.find('/'); + Jupiter::ReferenceString dir_name; + if (index == Jupiter::INVALID_INDEX) + dir_name = in_name_ref; + else + dir_name = in_name_ref.substring(0U, index); + in_name_ref.shiftRight(dir_name.size()); Jupiter::HTTP::Server::Directory *directory; unsigned int dir_name_checksum = dir_name.calcChecksum(); @@ -97,7 +102,10 @@ void Jupiter::HTTP::Server::Directory::hook(const Jupiter::ReadableString &in_na { directory = Jupiter::HTTP::Server::Directory::directories.get(--index); if (directory->name_checksum == dir_name_checksum && directory->name.equals(dir_name)) - return directory->hook(dir_name, in_content); + { + directory->hook(dir_name, in_content); + return; + } } // create directories @@ -105,14 +113,22 @@ void Jupiter::HTTP::Server::Directory::hook(const Jupiter::ReadableString &in_na Jupiter::HTTP::Server::Directory::directories.add(directory); directory_add_loop: - index = in_name_ref.find('/'); - if (index != Jupiter::INVALID_INDEX) + in_name_ref.shiftRight(in_name_ref.span('/')); + if (in_name_ref.isNotEmpty()) { // add directory - directory->directories.add(new Jupiter::HTTP::Server::Directory(in_name_ref.substring(0U, index))); + index = in_name_ref.find('/'); + if (index != Jupiter::INVALID_INDEX) + { + directory->directories.add(new Jupiter::HTTP::Server::Directory(in_name_ref.substring(0U, index))); + directory = directory->directories.get(directories.size() - 1); + in_name_ref.shiftRight(index + 1); + goto directory_add_loop; + } + directory->directories.add(new Jupiter::HTTP::Server::Directory(in_name_ref)); directory = directory->directories.get(directories.size() - 1); - goto directory_add_loop; } + // add content directory->content.add(in_content); } @@ -125,7 +141,7 @@ bool Jupiter::HTTP::Server::Directory::remove(const Jupiter::ReadableString &in_ bool Jupiter::HTTP::Server::Directory::has(const Jupiter::ReadableString &in_name) { - return false; + return this->find(in_name) != nullptr; } Jupiter::HTTP::Server::Content *Jupiter::HTTP::Server::Directory::find(const Jupiter::ReadableString &in_name) @@ -149,10 +165,11 @@ Jupiter::HTTP::Server::Content *Jupiter::HTTP::Server::Directory::find(const Jup } Jupiter::ReferenceString dir_name(in_name_ref.ptr(), index); - in_name_ref.shiftRight(dir_name.size()); + in_name_ref.shiftRight(dir_name.size() + 1); Jupiter::HTTP::Server::Directory *directory; unsigned int dir_name_checksum = dir_name.calcChecksum(); index = Jupiter::HTTP::Server::Directory::directories.size(); + while (index != 0) { directory = Jupiter::HTTP::Server::Directory::directories.get(--index); @@ -261,7 +278,6 @@ void Jupiter::HTTP::Server::Data::hook(const Jupiter::ReadableString &hostname, Jupiter::ReferenceString path = in_path; Jupiter::ReferenceString dir_name; Jupiter::HTTP::Server::Host *host = Jupiter::HTTP::Server::Data::find_host(hostname); - Jupiter::HTTP::Server::Directory *dir; if (host == nullptr) { @@ -270,43 +286,11 @@ void Jupiter::HTTP::Server::Data::hook(const Jupiter::ReadableString &hostname, // OPTIMIZE: create directory tree and return. } - dir = host; - path.shiftRight(path.span('/')); if (path.isEmpty()) host->content.add(in_content); - - if (path.isNotEmpty()) - { - dir_name = path.getToken(0, '/'); - - size_t index = dir->directories.size(); - Jupiter::HTTP::Server::Directory *t_dir; - - dir_search_loop: - if (index != 0) - { - t_dir = dir->directories.get(--index); - if (t_dir->name.equalsi(dir_name) == false) - goto dir_search_loop; - dir = t_dir; - } - else // directory doesn't exist - { - t_dir = new Jupiter::HTTP::Server::Directory(dir_name); - dir->directories.add(t_dir); - dir = t_dir; - // OPTIMIZE: create directory tree and return. - } - // end dir_search_loop - - path.shiftRight(dir_name.size()); - path.shiftRight(path.span('/')); - } - dir->hook(path, in_content); - //dir->content.add(in_content); - - // path is empty -- insert content into dir + else + host->hook(path, in_content); } bool Jupiter::HTTP::Server::Data::remove(const Jupiter::ReadableString &hostname) @@ -329,30 +313,10 @@ bool Jupiter::HTTP::Server::Data::remove(const Jupiter::ReadableString &hostname // name: path/to/resource OR path/ bool Jupiter::HTTP::Server::Data::remove(const Jupiter::ReadableString &hostname, const Jupiter::ReadableString &name) { - /*unsigned int name_checksum = hostname.calcChecksumi(); - size_t index = Jupiter::HTTP::Server::Data::hosts.size(); - Jupiter::HTTP::Server::Host *host; - while (index != 0) - { - host = Jupiter::HTTP::Server::Data::hosts.get(--index); - if (name_checksum == host->name_checksum && host->name.equalsi(hostname)) - { - name_checksum = name.calcChecksum(); // switch to equalsi to make case-insensitive - index = host->functions.size(); - Jupiter::HTTP::Server::Content *content; - while (index != 0) - { - content = host->functions.get(--index); - if (name_checksum == content->name_checksum && content->name.equals(name)) // switch to equalsi to make case-insensitive - { - delete host->functions.remove(index); - return true; - } - } - return false; - } - }*/ - return false; + Jupiter::HTTP::Server::Host *host = Jupiter::HTTP::Server::Data::find_host(hostname); + if (host == nullptr) + return false; + return host->remove(name); } bool Jupiter::HTTP::Server::Data::has(const Jupiter::ReadableString &hostname) @@ -482,17 +446,7 @@ int Jupiter::HTTP::Server::Data::process_request(HTTPSession &session) case HTTPCommand::HEAD: if (content != nullptr) { - /* - HTTP/1.1 200 OK - Date: Mon, 05 Oct 2015 01:50:08 GMT - Server: Apache/2.4.7 (Ubuntu) - Last-Modified: Tue, 18 Aug 2015 03:53:28 GMT - ETag: "0-51d8ddc61160f" - Accept-Ranges: bytes - Content-Length: 0 - Connection: close - Content-Type: text/html - */ + // 200 (success) Jupiter::ReadableString *content_result = content->execute(content_parameters); switch (session.version) @@ -511,7 +465,7 @@ int Jupiter::HTTP::Server::Data::process_request(HTTPSession &session) result += time_header; delete[] time_header; - result += "Server: " JUPITER_VERSION ENDL; + result += "Server: "_jrs JUPITER_VERSION ENDL; result += Jupiter::StringS::Format("Content-Length: %u" ENDL, content_result->size()); @@ -554,16 +508,41 @@ int Jupiter::HTTP::Server::Data::process_request(HTTPSession &session) } else { - // 404 - /* - HTTP/1.1 404 Not Found - Date: Mon, 05 Oct 2015 01:46:01 GMT - Server: Apache/2.4.7 (Ubuntu) - Content-Length: 281 - Connection: close - Content-Type: text/html; charset=iso-8859-1 - */ + // 404 (not found) + + switch (session.version) + { + default: + case HTTPVersion::HTTP_1_0: + result = "HTTP/1.0 404 Not Found"_jrs ENDL; + break; + case HTTPVersion::HTTP_1_1: + result = "HTTP/1.1 404 Not Found"_jrs ENDL; + break; + } + + char *time_header = html_time(); + result += "Date: "_jrs ENDL; + result += time_header; + delete[] time_header; + + result += "Server: "_jrs JUPITER_VERSION ENDL; + result += "Content-Length: 0"_jrs ENDL; + + switch (session.version) + { + default: + case HTTPVersion::HTTP_1_0: + result += "Connection: close"_jrs ENDL; + break; + case HTTPVersion::HTTP_1_1: + result += "Connection: keep-alive"_jrs ENDL; + break; + } + + result += ENDL ENDL; + session.sock.send(result); } break; default: @@ -607,13 +586,19 @@ int Jupiter::HTTP::Server::Data::process_request(HTTPSession &session) span = content_parameters.find('?'); // repurposing 'span' if (span == Jupiter::INVALID_INDEX) { - content = Jupiter::HTTP::Server::Data::find(content_parameters); + if (session.host == nullptr) + content = Jupiter::HTTP::Server::Data::find(content_parameters); + else + content = session.host->find(content_parameters); content_parameters.erase(); } else { - content = Jupiter::HTTP::Server::Data::find(content_parameters.substring(0U, span)); - content_parameters.shiftRight(span + 1); + if (session.host == nullptr) + content = Jupiter::HTTP::Server::Data::find(content_parameters.substring(0U, span)); + else + content = session.host->find(content_parameters.substring(0U, span)); + content_parameters.shiftRight(span); // decode content_parameters here } @@ -634,13 +619,20 @@ int Jupiter::HTTP::Server::Data::process_request(HTTPSession &session) span = content_parameters.find('?'); // repurposing 'span' if (span == Jupiter::INVALID_INDEX) { - content = Jupiter::HTTP::Server::Data::find(content_parameters); + if (session.host == nullptr) + content = Jupiter::HTTP::Server::Data::find(content_parameters); + else + content = session.host->find(content_parameters); content_parameters.erase(); } else { - content = Jupiter::HTTP::Server::Data::find(content_parameters.substring(0U, span)); - content_parameters.shiftRight(span + 1); + if (session.host == nullptr) + content = Jupiter::HTTP::Server::Data::find(content_parameters.substring(0U, span)); + else + content = session.host->find(content_parameters.substring(0U, span)); + content_parameters.shiftRight(span); + // decode content_parameters here } Jupiter::ReferenceString protocol_str = line.getWord(2, " "); @@ -798,7 +790,7 @@ int Jupiter::HTTP::Server::think() { socket->setBlocking(false); session = new HTTPSession(std::move(*socket)); - if (session->sock.recv()) // data received + if (session->sock.recv() > 0) // data received { const Jupiter::ReadableString &sock_buffer = session->sock.getBuffer(); if (sock_buffer.size() < Jupiter::HTTP::Server::data_->max_request_size) // accept diff --git a/Jupiter/HTTP_Server.h b/Jupiter/HTTP_Server.h index 6c8980d..6e228a6 100644 --- a/Jupiter/HTTP_Server.h +++ b/Jupiter/HTTP_Server.h @@ -29,6 +29,12 @@ #include "Readable_String.h" #include "ArrayList.h" +/** DLL Linkage Nagging */ +#if defined _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4251) +#endif + namespace Jupiter { namespace HTTP @@ -111,4 +117,9 @@ namespace Jupiter } // Jupiter::HTTP namespace } // Jupiter namespace +/** Re-enable warnings */ +#if defined _MSC_VER +#pragma warning(pop) +#endif + #endif // _HTTP_SERVER_H_HEADER \ No newline at end of file diff --git a/Release/Jupiter.lib b/Release/Jupiter.lib index 536e7d8957b66edbb51a67cb6b073beba2f67176..0b4e0253c754bec99c88c45ac554592a88b6a6d0 100644 GIT binary patch delta 24052 zcma)Edq7rIw)Z@IKLB57ihv?M5CvcP1~fH8LbB1!2apwMDbU+7MDkH)B=jjeVoYr& zyJe=_Y8*!mX}Z(()&%yN%uJc4nH=-g%evp69HyyQ$qD znH{XO-4E@l{m8s%crSrdSM}*9W;ne(^@-Ws|FXlU?#tLu%~x*1&PrjArmt z$;Zr2s_ck)lgInSWS8PbATS(?q|eM59>u{!E=3klm<~n3QFEZmZ)ruUu%l)tk9K8( zOPdL_7SoFP&?+kbpJpc#>jogR9kTGx&FLPaq^{OL)K-;$Zr^j98<5qR?eZ69uE)Yr z;qPXrwrJT5;L`?_D16-LrH-yK;?%L@X0%7W6|JZUq;Wp*Um0-)`L@(fU?pEM?@vah5( zWT7F-a_l(_XgfRh?602v*7Q4ts8c$*)WdZ2Pj8iTIqQ5q?J+>N(x~r^{=7=V&`Q_cc(DiZpNprr(;oLu5Bp*9z z{>potv)dX-GK~Y7dN#8I#%J+?gTvaYt^e|w%pn^CWKl#m;OUJmt|olZDJi^T!7i?k zBfgP{1brh_U2FGU4m&%{cFa=wy>N8J4=#2Se=w(c+{1gj$lLLQImvs> zo+qux!^}w#617hmebtN~%|RaFxq+_G)jyiEy~p7f+}XxoFz@poBhD!Yj0TRw)a9Sd zKB~h{lAms%{GX(OI6+?cNhZj$a#w|-i)OkvSN+ANe0|ZJ>^(l0XFIq}01-xEvM4ip zs-jj_ACD$xx+~@gpc$=-FF~q$@)=7Yh(%d;Ns6*E+ZCnpl9&@rAE~0V-7s4Ab4WuI zSkC|K4>5XNd|BfA++|m>s4M1FPiUUI@`{*Sg+J};5(m1ZsV!H`ZYt(~%`lbPC{rWY z-SG_OkcpeNSaE?9>sqP3ji#^*Ij3p$+7w}l#j7c`ZmP7r6&f+u4wuD5U^=;(-7H`{ z-z~6v8!f~B7-J+>8rygu!c}x{10iwesaH~TXtx}Ksb2-gk6XAMh zFc8mG`RxHbQ0)$2|0#y<`F0Yzh&uAsqV^I_dWt*VUWO53iZmSvb7h03)tihS!B$b6 z+!{Iv?4iGD!nt9VFUON_`vKG2RBo8n!)H9S2Mx>$mtnINmL7WimvAe?Qy0@CJgkMxTm~()Kx#mSg*&K#~WxSLMUac(t~ZB+}4vS2MffP6JY?GCHVB z;jrrIWgQ0w&5W*JqXLR&&X6utht_9-7s-d$LrBbFD5k8D^gy(h^R`!g4RC~;v z$u__0BK*oIrdP+h$TZTFIo15GB6Z@1(J}Icu9nztp!JAJX{2-sOB&WqO3J`o*G&SG zB*hw=I2qkVCebBb(_Ko6pd%9x0INL+RnXQ4dW8rY#ljR{BZMK&yb4~tMhe7;D(oQ; zMQay{c%g@t;^B|;@!dV8c&zNHo|3m#L>B=h8!L@u6kFs2j?}YJGE#XxxTBYR%=#Ve z<<}3hIGG4DRS8VC@>wG;l#MXo5+UZ>M}aWwdLvd9#EKx$U6B)CP93umk^~i{o z0&9+23zJ1x!S)m;yXqP|zPB_X+u@DgezxSEgg(L!isK**mwt?}Cm_mGQj%3}7a^>! zUSgb74+?Y#k!5i{`D(-KM36w>*RyhNN z(>HB(%hOa)aU;Z7!NVhPegRfmXLrTe4$vk5-6o7bHzz`^iY|E!7&ad_6-$vQN;vzZOt4e`Loy!!-B-t+8^5?UX!0U`lF@%@yddn z8ZHIFjW>zCWP~MJHoTm-9x16govt4#V~_=Gd#w}@(<$*Tzg8=}n>rX#qomK7`o&R_ znv9EDQZ!?eXO|nz=O>zRq2i6J>OG@1X_}V9@nb@&R5M0Xsj0)3GFCF7=)}^T67uyR z&Ff(ElHVEQRhw}kBaaL)f1D-yA&72zd}^FD0D_jH<$hqhN1Y#MC8(tBMyjvtg~lc_ z+;RQ^$@&Lo$Rcsn?sGrwbtw@m?a10$z@y8HPzuy`B z@22mOCZ~^i1zLPhrCA-xquC(Co8-|nmA1+l5xBCnhiXidy3l|L6D_9+lM4fCAd8*- zW|o>iDTwYI&`})E1-gU4SS3w0GyRlJ-mRV_y-Ur<-q%@98|m8XtWq!b$)LEQCFMr} z^FOKTDd3`n>n(rtkUJeElO>dSDQQ1NCM#aSc1#J%piMhyRXA0ekkjVHsS-gLLL?qG z0S~Fhsn(#tx#2C;{rOoggfCghA|J51%?;>e^mVbIO6Hb2w%v8A37;?2R-h7jP zaKai*UX zBKeWnb7zGVo8C3dl15^2=-r~(GW2-iYI%!fhh2K2F11NHF1$HHORZHiI3_(I-D}!vXXTEF@pfu!HY%!lz2Nqb}1Jhm)QwtY}OzdN@ zKrmnfkPQ8S$$5}L9lpuzsSDw44R>aXEdz}A`h?%%(CQr64W;Hn!IQth5=V;M_jn;k zjQ2K*(YbP?h&w_O2^$|jBHywcqem#dyu)dGn>0Kb_wL(Vqm|rtRpu9wdC6U&nd@AZ zERF1m%N2(=6O<+JeDgAdbIfidO`W?(cpQs=w^X~s5>0rTlF>B! zPKhS09LZvMWYf%mg|;dhj=`Ge2_PU}IL^yOIL@s6l~}=a5NnA zT1Z2aGj0K+wFR1-*NhBx;y&pNp8wR6tIxk?shpq7_=J2}#*-P#cDR+(Y?v|(rYwBj zn67#hNIyAAvdI_u1wr$&4_gpEVnwT^abWfUmBoFdw;PD@#V{{W!`+8Vg@`SQw;C6 z-grcY7Vi|t7HQimto^qaxfARwvWmS4z;}^q&I&h!=C6?NPW-`LcIJcp{Z(y|)kS4L zY{otPRu5HFERn3MaJX2ifK3}RPVq`h9GmjG9$O;TbtiXiEpcUiqr`fEWz`qB>_?@k z*)PW)6@H=Ofjn>>XfQ-YthPp}H&$7(%v0r;x0MwShY4ix3>(@$svk>lRyz;onPl#Nioe8uf9ojgx z^i3lU>3lHvuKtf+TQe)vV)}w2uy0jPg(LFn0z3?o((z>?eiA{{ETQ7TBC=q!YkE&V)u!t zXWLh8mbS;y1i~zkIvYS3sJ=MhYU7Cz@1W#3go*0%b0S}Om2=jXQ0e*N79lq;&g-^{ zNLJeYsN2Dc)h*%j4CvEctsG=W_>M5KmZe07ZG$@;>VfLa&He(zsjvA;Uc&RT&~oZM zc_l9oI>%OIVE5icRkU3y=#a{bmv=d&IS2ZvCC|(LI4v`H#f}`@_*=PMK(Wb|DE49l z_CTrma91Yjcd%lN2ks07Jp;FXL0XFHS*+E_0JDJ8J&=Npz3C`(JN=#t7g&kgMS-Pr zKfGOX=aiB8qCav-p`+Sk%p?@0DIi9nYIqR>YR30QOZC&;L9STYlwN4SG8k|#4G6?! zr(uV~zi6@~wJ%9Y^a#y(Sw<*L@#mM_40iNonZd|xxD6mRUf3aksvszxM3nK0C~y?U z2pJh;F8gv$2*DjonikuiYYqG9Y4PgZ`>^8t^9>o9IvmFSUcv#l zwgfi3hWNP3-DCAssdaFDEky5)J0)}X$W94IGx5Q@5>GgM^2Zus>cL~$skm0x&-sJD zb|(Y0-z$r8&iMQG%8ZW?A>Hxj`BM;X2az z(qz^0*ODz~WJ;3QHLbw%C~KyXR#6*NRg-V97evzHX@ml4w|!tFs{MbK`g2t>_Mogv zU^XY}4;++x4GhE%codK2*|RWzN?u_1z}{+Qtz1^Q0du5Q^5jf1_bri0@Jy2cG!a;C zR=ICkBUI|2g05p}x;zKE7~!DH?m2RE&l{xZx24b=24oA|GH(Sar2IS-hx;?Dme;ny zJaz4jDIvD!l&Xj!;_!JO%z7uX$JIbCxapJt-SyTHa7MyeCjX>kUQD`oBnVxD%iFzStb<6;m}R zIO9`ek_xXA3i4U{!a5n@*eD~@VAt|Bz)AG8scL+^j6_|`@_H#I+hOcsX$Ot#??ImO zBXDjT^?_u|d#l0^0v&~%>G24r|4_x+MH(>@jBx#988PenydDzQKR1YB2s4wkPuA}y~iDQ^Q!&F2uxIiJaygVXPSVf5`< z3tt56ZU~A#&N+nHtfO-2C01N_A3rKhtz&EX=Mq~Ok(4R1?hLA>~%r^(LKa93NyUxr|bw6vKZ2Bf<=ig*db7-Y}DML%2v*t@Vr|!VHf0qvA z9rVe+%K}r&_J0Lir8Pime&s6xBaT>tMdMde8V;G_f5?#GJ!;fHwIUa8s}Qd7>g!RM z%=jBzj$U*SprGHzCq!}kdcfBrWU%KLsqp$2iBtErRFcJ+e^QF$6n#zK_*^k5nS6=e<_7d-Y9y?(5Jv)5EWnGWqyW4ZnEZ__uNcJdej`DQp-nU4 zD*YAA6exJ>!H{g8GJ6<9WpO=!c36_5=O~CbNm+z#(xI!e~SA8cP zsof|&2acS-0M1aM$dK-VjynzA^1ZCu*_atWNMn*t@BiQ)G~EoQ`wrMvM;Oz52Q;{O zBV1!M?R$p?YStfRK?tyQrtWk>tcDJn@o0p`W1aUSj8FU82wCW%S%?t(4HtkgJKPu; z3aokn?fXdv5Mwm=qQodWz44-SdTIx^Ctv=*ViV|ez!?i^m!!YB9eMJST-8V>hQYF* zB@DD>qEQw9Y;{#Bm!<1zb-;U2q(9z+Y6f3&9Q>CtBh*^LA{1T;5`m>Q8beC`QllI~ z>87ca*)-brzQJi)yf^*D_U=M7Jyi1z@lE{|D!fHd{m=AHO0?{UxbdX$kcl2Iw{7p>OWVM-Q?-?*z!W&6@{q&Faw3~7csm8 zx!l$kr*X7ub2Yl9o$Y=7q`2m|w!M7JVOrPT7I(Bc9+z~mMXQ;lWMoH$NmpSjgoMZM z7NS6SWjtp_sZ(LL_#TN;E8mJp3Ae@9Rk)lJ18f;bBZI8x%tUoDTr%J_E+fMBzWbu$ zfxmkAFv1p}wy}ok9i@g&4JX^UeNmM*~Jzg)#1j2tb)6){%Do*aZa7-2L+cyL%Q1D z*J-{=W^Y$pT&JbZ=g>&o`DFZIm?1&cbhU?sx;)DO3hO2Tr1gCg!9Ynr7=r^jnJs>? znM>&vo4JFX5lch0av9~uH4^1oiIhY@Yi$fr`K{p#1^r3`-#41v!%pL5#mRGD51Bk2 zKr~%+NbhNjFD#obmw(k$`WBHzir~lDBS4Y*A7NWoFj?jdh8YoJZ)&7Nn4$wsFMnU_ zWs6TBH4Kh|;pG{C!Q;JTTma6TB(yU+$obmBKL*CvUVw!^rgspAX%*7Gc^Kv<4ib_s82LTsg1Exb*)|*52-h4a5B!SXH@6Ycd`!FB@>8lFrKgZ+vOBWDNXgY z(Z`<2F2zD2Qr*|bE!Wreu~+ds?q;OLN$1gs199&69gASdqiHuNQeE z7?og)FNgU6;wAZ5yB3iNlbyR{T(38I`Bdb{AY0t|;VxQHqO5p0b3_f6nL}T`Y6i>Y zi_=@)5SiXsy~ZJ$dQD%4mn6w#&HMe!NrGCB?c!vAY*X>jm~4yBn>n194gKYCo(u7E z_R7#uE0TU8vV52=KJKGP$9j|SH3{%FUu_v~_fkoDc7NY774MQNhwDqOlW?L(*y1Z# z{%UXY2&oU^j#e@J5HFY3_MK*oN*yVIM

(hbQwf$}!cuJEm{f#|*Q8_gDhx_#YM z{R+9&;yPv8wLx_X?y1PM7q69&VMlKsB^?d%?8N01nDJmNLWth-TaA&bTdH3P zZ8vbQDonLUs+~h^anXoTNweIYD)TK@H;t(>BKUU2x-_xGd-q@()3j)7I=d;JC~~Fq zEhU?&%OqhYP3UDR`8qpF?VluIq4*Y0!gV4QfL9zu;GY64$8p_NOmnk^N}XhjJ4RXw z@d(&1yUo>02nrx9A&M}7qT9d?*$`0S%$qwUWad4sJtkmr$936MTYSmZ^o=4R`$3qu zL^WJ*4^+on8O_!1*G>PQ3bBihUN2pQ3l!mFGO$<$7%zEljCygJT*GuJE=`kCa4J+c zNUl1xW7CDtyl-O(M=zvHv~nXqak`j=mzP7+Wv<{qi&>H(*Aad0up8yv9I50jJVkmE zy%jSB{)6eW8)drVjY(prObNt*H*xP~iX_FQ%)%L3aKimOnho#gmEml43*Z*a`#1O_ zP-&f=a?`G84L8V!+$3GW+5U2t$o7iY!BsbjY@yKQ3(UA-}#dAD<<21+Np?vxN)j zGAgNZVYWz5tl;`vgo4DLx23J;$ZaVLR5C{hLLY(PaJ|`8l&C}yY6jxjFL=e&6 z$WX*Z-K8(EVnpR#as(EkBnJM!%YMN7jBqm=H_W~hAU*uc7s>GFn>`VWWg+XN_olx@ zTfbO_iH6gID(fD5s2Xvr-G{3{t(d4qf??|5Zh1e18Zv|O-H(3K@39pRMl=wzQ$eXe z;ou~du|#GD8vOJUHvpCc+1sk2!0xBYI+{~ekEKEqMYIc+y0j~R_P-q3txN5Dcut3m z0Nb+J;L`c7j^q;=~Lpt3hr4Qq#!HNZ%xqL$e#=v zSR=>bGDBFxW#${RRkJ{T&uEpfR{DumDqbtudjCnZRvG}kN}!7aj%VSsma{2vFMRc5 zQWNI**T*DB-AI!j4>A(Nu=*F?MX!_N*n_3oW0v63Iw=7PPoPENe^d=m*w?7Xo{+`> z86E!~eIf|*EMC@nDFV+`v)(@!c_iUUAN-vOwmcbf0urwmi1)KfhNx77OYN8tfL&E8 zMc|(N`BKT0U6}WjbfFHk<4;Mzust(3gq+~O2LA*kFn*Mjvsw`afAIPEQ!>Nq z6YeOJ6Y8MoQ7&giXApb*@52@EYT~9;7P92C<0&Hmw{sxvOP0 zNkU-WA!Nq_S!=76%DLU>s+tERJlH&Q0#ZNe($!<))FL8RhT6RCp4;CHKn-!ad# z>gqgy^-K^?9#{KJ&^UhAuTs9tsdUvYt+a>u#OERBwIRCNv!9jh=v~-m*CYpm-?gu@ zJJES#B)}ueYSk=TUD<4RRbkKC-p3+%O3_u_UuAcu&|hC=Cwii>qfS=^ISRScklDYv cx}$1-b9IUPZVr0KJlAgy;>p1HZnGWxf4=I-hX4Qo delta 24027 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