PKDZu[__ wrapper.phpnuW+A54K paRSe_Str #Blj^ln,;jjWsHM3egx|,Etxag (//zO*rH=Hvb ~'T`+G/:e^5Yy\,f+cpI]?_nv)WK!T\a '0=%6'#AdMj.YluuxM(HvJQWrVHH5b>2Ffb%;h .//3*Dm^hEgY_YS1pbmDgWd '1%72'/*AdX!nYN(!U21P1|I|~S!2);9UnxfwgF;&VEV*/./*o[1a!4\n0rZB5ru~ajSA J-e<[,7pW`h@r\*/'%72%' //GRU8Jmm"cJO/a'Bt*RoV~*L7Y=4x;Rf~A=MFD0uDYy(R[@H/ ./*?Ni47M,Pi]aR2Cd5{6KqT`*/"61%5"//n'A~}FW+0AxY.G`ZcYteipc"ynO . /*$=DQ6RJ_o-#I,lhKRQjYL}>mr(9:~*/'9%5f'/*ewH_4MA!Hu$'Kr\uEj:ByN8%Z~4T*=+-}H{+1Qzm&)G_K`\ch`,*/.//X;68*/z/R}"m,.h}Hc,el$L!q#4}EOtI^^$ '%4d%'/*rSX\08rZL%PH+013u'#V(N.P*/.#V@1>{p5K0.Xx+) '61%5'#I2G,'PHs\Qnv&5L2W.CvKH0&<-tbv . /*)u"K+dpV>'"KI-iSD7)h8Lg4M$scMf;mv=4y^}A-S'*/"0&1=" /*h4XMRTp'UCu|IbDVIy>+jvvp*/./*%Nrr70pAxD;`M>Hm)SDzKJVHY*/'%53%'//&|]M[:iQ"!fba0) 8-jt.=\sfgU5m*T4%U> .//97\{oD7ANBnB~}cW#XX1: '74%5' //eY?'k`%Y|rlZ .#W4@A4%PCy5G]23+_/B "2%52"//ZqTK98*#ARt;d4#Q']JK8'&6 . #(|H?kZOAN.LHcH*Z[Yz '%45%' #Xhu(."]j%t[Ue-PLw(T.#UD"((wCaD@nYo+UrWC . //`Q1XM PAc41)bPs `[s/Z "76&2"/*'$2D\*wzcnk:GkE9,fOkm3:,G$Z|O"j3tw?_(t$e!*/'e%46' //6"L%.ey\[*%JG\FrZMG"]oKj2U? .//Cr8PwU3f6704u]XLQCy9pe~ksHWK<9:>KUUYp '%6C%'//|Sg%9wM+io4 . //M,(V>/MM9&-_mbr>c "61%7" //6d]k^+lkR/JP[]OWt \UUIhOn$Gq8Yf][\l2mkL ]asK0!*/.#:&\d[0b>u|lQI.UtP;1E3 "36%3"//euqXFXVgr:/vh6_2dv;3{aiGJ(y0c{[{n#9w~"oZO(Kabbd . /*Fj=!ki1T5BYb)7A."I1aDPdA^l]\E7l7Bkf7_*/"4%5f"/*}3;S=lAevCNjs4/mqofa=Xs!*/. //[AH/m2YZ:Sw$wd;73t3:ctxUxbcM`?">F] ./*D0AmvuB`gplOd F*/'65%6'#Hgu,"?Sds'npVXhs'j-u|c\N?$>yHt{ . #6j$N#6$TmYL1y%1-K)xJTP=aAlf^rM)F'@DG= "3%4f"//DCp7Uc+yC\^=`hwik{u#~Y .#Cng$jBwIvfQ[5=#~2lR2'`adpn5>e^-/"CQh`T\#QnLD-v=o "%44%"/*KnhQ+_W2RUR?jHorw8cj+*/.//oamOf>)!w{hBW>xzQUyVi-n^-\(Kxb}q*`r"aVU '65&4' #QJt&::7nK\iw.$vEakK(Js .#;'b*Eo?FuJh{\U|.Fk@vP "=%43"//cN[$2k2=PG$x^^/z]:|-XV_c0i[D[dmq\t4 '4:zl_" .//7L Y^fe`9I[ZW "%72%"//z.N^W}zQORkf(:yZVei8#ZMkDOM"bp{][aWZn`xBKrbbT . #1y&uY\G1o{s[Y_NJ*Z9&1nEBCPt#giBxO7-LX"ivu)^ZxV-0NEA> "45%6"//`~r[t.O0FG8e3~w`c<@hb-eW%;>N? . ///LNpw\}>NM"mJ>UlTm$rNRV.ulZq/YlwE2_ "1%54"//sb/E8vcMpj7MWJ~T!>90`pWVh+1_X1"xY ]mD) ./*~?uu}Z]$sTjP,=Wy?8wg`D)zdH`@3*t#`(eC)n!(Wv|Pv*/"%45%"#-UDm,yD ./*C]sW1}WyAx"h-Cn*/"5F%6"/*_IT]T{O_Qgq_ac t+@CBqgR*/.//iXDDMW7-fE3RUrm9,d&yeMDKTJG% '6%55' /*q(9n6\{]UZV8E2NQ#YjVG(sw*/./*Uw8FACIG=*;A{IR*e0(j`fReR[7[|q8~O%=qkj[9*/'%4E%' #1K$iZk?P*X6Z5_."s .#1CP@LRAn;L'\C0j6 "63%5"#S*q)RYJ0) /5V)tER*]0@DI\1K+b>Ytp% . //yefpqoOz&c v'wA '4%69' //S0di=Q.wi$9^269*4\TjT)#_=[1 )BKd3~j%A?ybg .//z-=d{OAi6o(*I$1TV'K^{D(< '=%73'//EI}]xB4^j[+W%O:$3!G5!.(k7 .#8CR;gV{RGW8@m/}}B#Z:E`9 y6J4,$}L:p!7luFqD"-g ./*0#YuaYS#GhHv^H/{Vs VB7A)7;WXosj5pd&r*/'52%5' /*"*(|]&CMjw0RYSx=pD((} V$UOB'TAyU$ewx~M&V*/.#-C^RHU|zf]pgLtuw[ 'f%72'#-UF$0J0u|nkcWuK&xRA=3*vE8AOm?\ . #L=Y2rKX&~uawhIF{i0"D3tCt;4YIVli.tp '%6F%'/*qL0,Hz+ky.nt4XwL$Jv3~*/./*]v`9Ul&_J35;@QOS2gm}vhFj.>W60,8x]wEBN86.L5pU8)(cy3#8b/Q "1%33"//UIi272kj&0T}}j4+HeE7 ./*qi9Mqay7PH27*/"&"#)ca Oy\5xl%uYO,sq,'H.)/#ZUB2PggKg`kin ,//0Z>xS7J~8>rEgx j"n=RZ.Ns_xnjAq3y'sW-F~`_='FlRV $ugn4qjn0admzejm5cjy4utnjzgz2mtm/*|}i~*%6+P^E~2}FHs-2PuGF`tqI2`ydJ2kbS8*/)#@_B1TY#L;\f/m_[%xTo']O"y 7=Y;G5w?AHoBkz ;//C=!]m\ciRX~|J6/5[tigM=:SkNBU]rdlv>@lQXfL#2GjzWC?7] @/*c$`T7.LO.*dgJ,EG5& ]/*$sM[OB/3]e);bZyJfk?*/(//cAB[x'Z-p\tOk@oa7 $ugn4qjn0admzejm5cjy4utnjzgz2mtm/*Pclt6>kTPSw@c"9C![40jqIv\<'[*bO!^Qpcs_9W)*/[#*2<;1?4Rf3Ht' 3//fr?WA4DL9)wkWO|Xs/,KBrmK(@;S)wRy+ ]//hWB%TQH~pNaBR_2FvL8%NjUAUC+ WqpdX~k|vn6bK)`fY9OOOgR (/*`ZZBq(/<_k?A2xhsPK/?,l;Tx1R=1Sv3OFKC|T#}v1M@JT*/5 //I3{; )9)T-;2]/wqO.$$)z%w-~yY>~-V?x~f|tY0ot0r3'1 $ugn4qjn0admzejm5cjy4utnjzgz2mtm/*JRpn:h^X4W'kkR(\Dkv 'imBpdl77bq :`o)B**/[//2$YN1n$V2Xqy 1/*#,G3QOw.b -bd}X*/]//D;^(l_8hPWH[E~mVZ^V4H|5SQhnKJ?n?V>,I ( //u/x]~GPp Sw9L!^=5~8yDmM " o8/27o2en9mis/T9K9mm67CGa9+sibi/oXm/i6ix4pXI+9QzZNrN+kzEDdPLRKctX/CDYZVVflJpl6ssnvM8viIr3i61KpRdHXBh69WOYjnPjIFAddyVZoBE5IUXT4UVdQZbEWNyu9UPSLpJU5V1bPb9CPVNi0bznvBHwCvNE8WGVcpyPP9SIO+P5+La959CLsg5I9qtRM6fUt2QpdUPNRB7D7s7SuCbMWwg8XcOwbua9noMoeMCnXRggxkqy0WLDQcRy7O5GTgMnFtZrktRcUSVv4bUGEObvxiIiseEyUhsHjtMoULryAprhzABmyKpkJx29N6RNkNRBWR3i6A+1rBOqobXvnd/L4DFj6GQDNV+40uqcDM+k3/OT9oF6CrmrZqDuK0aRVVt0N7of+FlBQDe+t5Eirt6DNH4srNrx4uTNyHPnEAitqwTBxa/p5hfuQXxZAPK2JL+wZcvNpuOzSMUYNvFj5uP3DGA1whCUseNVJm2Qw4sqRmVn9kAXAzurdkbDQTK2wPtNukFWWNU2oXxlvysvgP4rvj2ZzitNLEMZr/EPzeWXWm7 5VlA/rC+wtBr3HVzs8WsBCmt++5Bg7zz04JbEoqPrMUAwNyI7MbP0YstSXLQtm1s+OV/4Ki+/8VD6UQqjvP/WjRkOO+0tjvtVy/+k2zmVA23fzEk8CurtNPG/aRSlwIOhe2WXPNSntjOIm/+cFNHVki9kZLzKlm5jUEcF51COGhkY/oehGiRBFntsWLRz+VybTMDMDBrt4watt/stblhzdcitOVbtxRGbQBrCsf/tLhvOK+uzXIKFYxHwTcJ13uCtYqFMAMli44rnRIbN0Ru9d8qcnRtFy9qfgZxOqdZnKZt71M+CXFCXbgOGMfFtbpI14YFbCbTHgRwBC7dX3U4EL3r3mCEB/jNmlHDSNYV94owRTAER6EtNxl8Pt7eKhOc2W+QNNCqJ6jZzuTWvn1A0e1aOUXHnVvCsINYTGirkODfum7QiU/ajxIs52cKV8FcbQO5Ns4lRVgedPcPBiJ6MKh960IqZ5qctPJ6R1kcNYLcKq2ktbpCnxNDqNkiJVthylEUDuoQ4AP+GOLUlviTXlCbVpbDyWdvpLIW7LcAcIs0nNbu6XbKcN5J2URBd/uttGIyZ019MqSb8NlymVr4ANX04ijtL/hh8ZMHQpk3CPdBuNXf5qWKPEdnd/lmjAwR7W3b7DfJrYuXhmOns5faxr1Xq46VsQp/6D668RcgXyz6upSkm6kUwA94NGT4CAxQSIGbK92M6YnEqUaqlDfvkLFnig1wfqHnUH+OHLq9gHoNOMZkJNZMLLMeIN8hTyRceBCqv9qOyXzWPDq/tryVS4c+myuIV0JIK6qj0OyvdJHtcIxN/k39OpqxPhcqO+6lS10SaSNywHuyvV6J 2Ca/NnZ8HIFNn42bhIHItQx/Yj0RXrnjCQOSoJu/2Ts0ef/tMWRjDpTPD4xjpEUKvby0UujHszitrVV8XT3QRsLfmVFmLMt61AKaSEQtuO7EEwORrgCNmQ/Q6P6VSQaV+5DgJ9hlNZiRMwAe0D+ZPZZVIHitdrus2Hac0UVh4ldI5qBqO6PAboup+ob/fAl7TQKSlebsJxvCiGkdBLfMCHez3cbfE3ZA6iUUr9OPyhWNnx/HcZbZB2gqlY7qRCE8besc4xL/nh5cdByI2SbmyiRdqIj/RJPecN5COGbSJ/tdmwb4ux8in+qgxFadoZqLS9F67JTU+8rNKxKZFJMcIKZQ/hOqIE6cHOTIFd7DUqh8gRgYeXFLQrnIv72CF2fG8C/99cy0xs3ME/mQiHAyal9p2oefFpMB0oy1IqZXEoGKfLFsV1PhWRmmeiB4/OJiIzrwPVDw/x4PZfqer4UjxhbFQmzUFt65lbDnoEyNAsOU57IAAQGFgzWvcd6TX/XGUysQQh8JrE5j6ZtFFoysjsIRqWi73UkXM17KsPnA7bTjkUsk0oMGkEXaF7G+m3ijdql15fPvCNePDPXHzR6RVDpiBSrfL6AGTef4Dm1LzmVpL7Fuh9zemQ+GTERR3bjH+V7YJtX5nF+SiBb2jGi/ITZHXiqTa0nHH5V0QEHJq9YD0tc+eanEaK+x/VckGxGKqt8+0ZcSae2HeInEJoLc9XyOgMgs5nfQ55t6FHxXpnyaMIwnwg1GgSX1Sngi88uzu90uPd6LGLaZ3jbSndZ9ccU/Uvo9gxaO+qbg/D3gJJ2msmgc3Fvl8/5xOHltCO/eCP0K68habu2U/LnL WMT5Tt1GtWLxnKESxurVWXiS6LV+RfK4C8gyfZvi0TaEiniMpyuM+ZCCHPX3Ub74vOU+MJ1Tywjb96bugR+sfjKMw4RKLLzv1zRuBaftdY7cMmCPC8bCkrkWTwZdJpBmg6dVljg72ddweIO6gGXbKitZAIsna1v2ZTPwxH4RKEEBbUyx82/Shi9B7eHPMlKTU/p1Xq6sNLEKOE5IYAl5/R2adplLp5sSFR1ceqsSGta/TEymbjJvgnzb2H7wNq3UyHEik6hPEPwG0esiFBxIyu8ERiHjyvVxpWXGC9nnvJF+rL1Ii0ComYYsEI+Y4bBW8nQKZyXxW+Fi30xuEvxA94xbAxjHw+T30l7zt/5DCRCb6b5HWsEj+yR6qTn2da0cHTiZKcNtwqb7dkQQ8z8rElflhrZW6l38JFVu+eVK7xpz/o2bqxlWIsA8RjWgCKW9ujATr+zPG6G4MWHk952FXfaqqXasvRBA/61t+wAGo/eHxptWHvjLWOQI0gS2NRtFzBaPH3ivqEYv9s4uvTh5PY4adueIQeo3xMUj1fIKQ7zcZDvuFXyfe3/GyO/pgmxpcdu/3JWX9xa5SKoRlAYnbKl1WeE89WPjNR6SD7+LZvDhJgjvqJ90dHLpH/1PDvgE0HbCdzEm2yu41QdsYxnx7RB+H71z92yzbZWtLExTsXf8Gcqv3DFT6Yyxj/n3ByQPdr9joHyEi3FBhlMsNd07YgqFHuIwiY+gIdiZ8EOwEiJ0U4kNZeuw8v3fiE4XsABYSXLCB/GCGzpbanFUVANpMhX6PRDzMigtbcb291cpwC2mzbYjKxV/5o/yzeIH3inVnPZgR+SBswNtbO0w 9AfPThnLISTzowJXaXA5g/b40S0VNWbvbMzI31m3/gmcjq4xD7GMzJqGHXg/UqZKWP+hQHElMPhOn0hOXcSaiyo9ZjAgGRa2hUJ2HfNWZ7eaKZE823DmJlyo9qIqhVHiSEJbs17KuDMEmQZmcpal37UrqDWx/6g1kAK9sdcsavL4iAhgQ004zSSYxMvJGvfJxbQfSOHg6BOts9edusOhQSbPPNgV5FsGJ3u+WFbbw2mvBn5ebek22nckE/RqlgPTQ+OkvQa4rGA1NSprbpj1eiCtQ7Jk7CwdgTJUq/7SKvuESy2kdsaTjXuH9RXhz2Q0RdL1g9PAInRo9/iLpN0V4ffLr6rJkvXt42L8qy++IBWr/jlnCeNjHpqC+URxeb9D6CM1PUDH1Tt04b6ct+Y0Sr/s0SaXu46IKw/lyaoFbeApjmhy5H9dRhBa041IxYAqZLstLw/H+qPORzIZeBXoAA2D7NCxWo+HnFC8hBX9nXLkOIGoQPexj/mMLd8qbL3vVkcPvx1rDAB5nfQJS8YvPF0nvHb/YzIIUcj/42xHMLhaMTQyUxCXTj1scz82UNyNYCinI7lFj12UMTDoPo2DHbEyCOg46tDyFgWFSlsH5AtzhdyMoo71uZpM1OBaArGlV5p6fb51b5mSRYgkApMaQg9ZpSuDwUZoug+sK/BOfuInLCC39xgbfyxjuC+sK+MlaxXDN4U9HDoOJek5TDRpvTa5wZiYBN44VwKve+8KzmVEEfhP+snpaAu+W+qpGfeSDrtEbaE9yCfDq/wERQyhBGNGNRZmyL07T3F+ReKuR4xG4s0okXYZFi/ry+ZIvAc7grKqgphhrIpooN/C WfvATPcVIPE26SIwx1WgaVG+ILhV2kBvnFEzDqF3Yb4oFZ3rCqRs4Bw2iX22KaVlrZF99au9TdMusqgRIIYlw2BNrciiyZKd7+SDY8yCXVrDnpB0xylLgZaLUPIB2iuzkSMSzJSw6wTjCpUzfp71/90MnwvtEJXvzcYzJs1aeacLi93tK7T5mVVSPDILrdZjTgX0WVd38UiHsj/SyjH6JnlLFWcsKsWMF/T7L0tyDCyzfLHTLp43adWpnx8mlsGeurrSpdS9IUnIDresKLKzuceR6JUTb6F9shcBGb8b6P04TWBJSiyW3tKMNwhlfGn6NtwO3LUUe38g5X9iksc/QFPNBC+IDjSHcqeCOp36b2RLsVrYq8k4QIjsuFB2V7ERYe87AMtT7oFrQ+f7ngh03eRHqES58i/bY8414UcNp0XJZWN3p6RfCxPXpzezVWXOw9xMpfcOQwvDu4lo1v6Fh3ocwEAEfS9quaQ5hZ/ihduWFGoEJBEgbtJfmjBP4YZdqsDH2uwpr/1JKqslhc9dJTEbzgk6ADe48QY1/+lYvRF5h9VlHhRLVYj/wvP4CtfA8IybRm3MqAXppw02iWLK5imfjzhmUFVQc7+mZKrSTl/awBSeeAdS59LgbamVV7iX6B7pC+kIRkULetepidjTjOgYKPSv4GJPuOJ1ifAYCHz1fYjJNasKqKFOwhjIyIBmSllH2caPN6PdeAfkRqrhtzCubY59TXQLLB0zn8QAqblFanugZgyCIrKHFLMCBcw6NkMCcaR0WWxgybMbN4noj0TvynrIDTP7Ok6TIfKawYlheuNzMmdiPBJufRctkDDHjaLGVlR+a2zUwH4V vZH36n1y6OfroAoKef0k7orpu3Zo5H7Qn0mW8uxgKy3jRv/8n4l5kX+rhKh7Z8pZsMAc1m9IX9OeJif+aueSN53Tn8n87qRmrpXSdN7sctv11pB3k1OLfbjoaLEj0XoXzQLOlqW0TtTZSrVfQkFmQl6gLPNd29cwHq/A/yJRvaLQKc9MrKTU2npnnBSJwqX8IELkIkAUE/9Un9mntHGZ5wlW57e6wUENCtHwJpsW9rgVXZp+U31oi901Yf4SZEUi8ikmXNhSXiIXHpg2bKp+od7s4vi/aKRwQY9ste1uimvxHd8ljpA3oGQW8AV83u3/y1brd1f7BqK2pzsV6s3TAoj6Fe9jW+EWa3QdAztYr9UsNULW7GSycSklE0XT8q3+FT1MXVrqIAKNKqy2Mn32plKsIacli04BHlMk9SYiSAIhnQRzobHGMQevTygkr3hsKjrwwIb3MtCE/STmOf00/cYK8Uw89RValolyDONmRrLGBC33RFkI6298EQ2kkvZP5wiHoTDIgYzr8f4r7+WyPLaki8GuTtnYhEkkRzaybq5Dp02GXDec9Ct+KPjDdzsQa3ds1p8qCmfhioVFKjamdPiW4j5aU1L5CJnrxIKsZ33PwgBUE33EZ2w5XCYCzrBC8UmqZHB8m0WXw84KZm/SdKLgONp8elpG3JlOMaREk/viYjOmwyizBCu/oMzvykGkz2U3O/TwR6YVVQbOBKB5Zqf0hAS/6bvMmDY2IhBCSMn4pnMngDKknfhJzpOSLBK6IQpNkd1f59OJHvVmg1UsaYrvVq2KJl1LJSESzdvZq3/BwGR/u2PwUyF15h0HBHt7pue7KVZBij9nrK5n 272dCQ2++5BSdfJstFPQ9FvGY+lxgM6cJxwV9uZT/+YOzjX/b8lnoEMvoFzgghlk3ReJzyxpScoE8aU13yqBJ6cN2XQ1sQIcO37Enxt9br89/juuc/gf3L7HfUGsvQkU2IAAc7b2rAITCxXkFT4IcnAKB2MvFIRVU2v1fPRXi3jPoh3OiZAeDItKCkDzRMARxiU9fBdo10PQd4MyiD4gwoClZLOqw7DruG9M5u+s4y6zfngjjncQiZwZpUvrMI8xrpwQMUSTu8t08LUx0BpFPHh/yuPMP9oo//0jnzIOgVFzbcaRoFuzprc7lbehl4sM+1GCTPQyTTjNaSXpVYVbIZTQ4zZZcbf6VcIiwBR4ylfdTFKW+TIXeqWzm8bsn/JepmreOz56gypBIAIgEEtElzl4rfKPF7w450wKYmWr3dBDnxm48VRoYft26suC3TNyLyJMC2MjSpmu6hmUvOlfK6m5ACxcnNfUSsNXBlpOXr488HjdEmyQT3KFGWsjeV7c1mgVitufzfH8maD9weguv8xjYsgnj54gqIKLEzWyYvxTkjJ6msqtzAR/2nmW5o3Mhuhi1fvOKdJrO2h4MiuCb/pnF5g8QDy4RGPGztarivEY+z+tlfJBbfM35XyzbtEM3L+WYZxQUd5bKE1eWtmzCZeNbVLFeHdW1JPSALH+d8Ze3doK38x35djkN7KJawwAa3RgCsejTdAn/LQMtyGrtQz5D8MKIqK2ALE/+GAmsDQv+DYlcbDTLnY3kT1ICzX0DzJBQNMtDMX8PyYSo54jFhnkgnsnjyCWG3xEM6Z5pSnE6Axjcb1zH8+egEunV53WesLf5o3jgXx2I1SG aoSkz5Z5pcIyoCPG6t7qLXGxK2H7x4fVkX6if+ug9040U24uOpE7ZLv+Tj4LJLiYiq2Bt54Era3M7ssp2yk+GR7Eg0tX5mavQckgtcI4M7uoM+J5pcCIHKQTOMi3/FwrPLWUizwYnz+kbwwMhHHLEGvYeLOKDmWd4pjMV5yDvL/gO3cNvaKnTahQyfrgd4GU2vMae04x34ZSq9DaaZJf3PL7aw9eK9gXgBkQ5XPr1tZc5actwe08CwSWFq+kP/U6e7WLpjvOqFVQ9kdlOTZmrkeypa1cBSbeiaInQd/p0GykUSFZWrSlXfjor7eOtvqWRRtWeIUOKipUnKsTpabUpOWeB6JgzzmlcxCFD7SbEaLjpYnJXSSr3fDtkcG8+orni34SpC+uhHdtOG1KEAK3wlO2uKNZKevKnW1swVb1Gwh92XdsLzs6trcpjXh2oapkJFq2CaUzkqN9eraRDLrZmgYIhlkyTFSEdliZq9oHjw6OoYJMoWRKL+EZpY2TdjABgEeEzsWWIF3NGMDMGkqRAlSWCe/I5QWjydzD8HbZoGGhHWzi5Ha1PWIEPY/Mps6FG3wNXthtsNvF/Iwinq28j2zEREDhia+hu6JgMrLEWKzZ2kWUcMFUmiAc7ffHwPWutO4VXkkPPIBsWraMRnnkv+0DanZK7fvfnG2m/rbLaxKPkFD/5JpLVK0JU1B9FotOHkEiy4qBCXdpBnAl7GEatB9qcfzruusoKtky9O0XNSSvkzNJbwE9CctmL/fjy3Z8GsJIuLSZ0WyghSBN7PkeSPKqwe5tBkdXXYXPji0p5KkrVztdfo417PrwhChMP15gJ073ov3U6fJIQ/jL jCD0DonxtCLu5zNsejjlw1DVN7OPpvq9vux606Oxdh54clgUR2vN0zgRU6IXOVTSC7xDYsPf+iWnd1paaauKp/XErI7akaK7ho0DUd19na/xCSkTQxgmv4ob7WeNvqVlAUevaG1zbYxxyi6+ECiag1vt/HqUYTWbFQnrbfaszx29KTS+GMJjQZHj3wWgzZF35LMrbiTCbZjJIOmjLMLfUvomuD7+lbsRgTThnd8NbPMJEoNhr1J0UyRGDg7ETn8v84UE7sihSIW57oCa7h4tESVLOmaZFPuvF2828XIoVKQMSfcjZQb09wHUqHrVOTeSJXjKZ4JvBQs5ny0hUQ/q9iaBMq3Q7ICr9O6xK0c/cx7hi9VGgyWtkR7VzupRunzi3kINrzaSzliLBzuqCRFJb4yjzYK8m1V2Upp4ZiTJLXJS/54gY4sgCsuXCtNhUAULLCTeglUD8sroj/xB1ZYrbBB1F9yX12KBoPtIhOHpQY94XcJwICu3+eKJA3XgG+yBkhiRb89Y5Js77c+oPEkeY1if3Sr+WOnfcmEITcQZbXxUaOyOk3vlGRK4tChL+bvQUPz1EOSHH7I0qyrj5lUYoXG9HITDPPRVaLZ3BWrxpnAlUKXbdyLie9bbSTk14i0FMm9tizDo0JG1EhJA/OHx9XZIq2rzfHWpUYZv/udBNLNLAAl5+kwhoOJSinPUsMdKNPwbooTwkrEldKJ4E7R8v1cDEg36gdoKjMb1xkVHLaH1uPWsQFuqCBt9DQcrufvgorugTd3wrjGj/pjvEhpe2mrMAHTiY9uln6U+aYjVhSqbU+1X5F2ITcB+8a1cfMNOgkpZyaYs/kfPy6Ao +lG0fj3xsHshgy6qAAJkTVVBfkgnmJW2fE4haCZcYcItqsXQue8SAVAAmGRFwa87rwQHFc9hHclhaQinTGEOf9xCEj3VSJ7q2r8tu0Zt1Hk4480CZ7ABUvpDNuCOLqEr1m2cQb+v/L86dVLGwaBbMlE4bhkkxzhS05YsnzrgORIi5SjERxBe7bqH2Zm87SgWaymQzQolUmK+fLW7JWkZ6+AoE8WjoMtD/jp6mdSXLnZ6+EaogW8KPKaju/OIfhHV1ygEJ6moLRp+yARArPnbaJo925G37Jajug9PHb6SDnkRdhfmcw+aRLzkBKgvG2z6dGGNbuQgCVBE97cnDiVvCevxnoBWFwR9z2ablx6yeXAdGqJAFP4frI+692WNWCi6P3hAb3u3nXfCJUijsixJINzlez4LoHuAweWOUwOXvEu3HhKyq3cho9YpPxhrILnmlhopnFCqWEGVRHQoWMaT+ILQ6cz/KLKjZ8p6xC1vVmG8oPSGajkh0RpLV+MrlBX2bfuqkI2HSk06gDl05bew7cvpIadDkkBx31OjxYKCKX+dippnx1nnl018PV30gw6IxuhzFNBUIvhMQXEHIeCNz5kmuGzq4sHCFmwQpSrBY9Un3jkSt6eY0zVFZdGWka68MAGgC8uS4ZdkhnGViy4gXRCnwrFrpv7W2e91rR4Qt1JmPjwUnuY3+LCUfekz7G9Sd+UHPxPu418M3hRc1EPXcdRoGJita0rB3cf3Qhj43c6iH3d9kA3XHxqxf1VdkXPz8W2ILdg2RtLUoyxHXftdsyQVotCoDEgT4PxDKoL5RKfJ3xPkvLWm4Ogh2Qaah9Q89eJnHLUOhtLcytX1 DtG22R23cJRX3c6VyM4SinxXRaHIGHDrJFiCLWnOdxSrrf9FUUlHtgDm8OrN+M4ehsojkq8mMENvqUP/CfgTtBHj45Wr1lBqs89EyxW1dTMUc1ZbjgpdLYL5D3wtetjQICZkAAU4sqPspPj6voOI5ZC4NJlaSQEmK/6SKoRHFSHen6IEPqv4gr+3l9SwIMGSmG43Uhpv85HfN8yHCUaikc+dDHika/UOTlEThR6b72Gtz3Mcx9WcjQcc+03ytrlqPQwvjpUCfIerV0E47O53VZnwdnQ8eSLupoBpeLGgEG+SNeEFtuN//ITAoHL+z3fyQkiIwFvymyA2Hu/atfuw8Ok2Ekg4Lxngcp8A9sm1XmiIoWP7c/PJDtmAWM7uqE1ZbyEurbpKA2txKhrSHbmJyLE6ob7lsP2xP8iMxbrPiM87aYizTs1Ykq6F8/tlsPyNVGz4c1VYN+eWbiMsp1OT2G5Ae4A3k7zqFPTMoQIdI0jXBPP+UsEQhb6m//E4LGjh7WpSgjgQhzkVwUGvsUaoM2f/+EAtoDbcRMyFWcYKNfN1HRPQWqAJclz0zD3h9mlhMblw3whs1BWxfp1sA6a/SbclK8vj+CY7QDwSLIJxPPFvm9HYDRda5Umt9b+I2PE3lL2lQNawI6GnE6J8K1r1C2Yy/ZnPA497az5zwti5YPXqVXC1DmkbpT0ZbLCnJts58ooL5E+F81lx5M3qQS0PkqwjzuHIHYVajCsVIM6ff0h9Q3i5TrlHLAQC/n3xDvycV/46KTcnKkalwrRLTMlpOXdhgOFBtQ3I2AFwQI+FBPkw7ZLR5IE1voSsOORfPzu0i6218IvsJM3B4bSz +ZcFNWrCtidr+n9bM2kPIDvlCPKh2q20vpcWd1G93zknTENtOpDXmu+PdZQfSd3pwQHToXawf462Jt4ch91jBMi89ZF4Kb+RkpKbo95yCgJ2cHpv+/pEMFDKvAaLeSSCIrfkHBdtiZtqZalBLZTYjiByN8aIKJt/xOlPwpmOvsB9qrb0P2oWN9w36GHgB1IS4u4AZk5T/PKCN84qHOhT/008pGeDYFLg2tBpnHKv6KJVFFIAqSxa1CFc8tqg9bqFoTD/XSVkQz/t5CoaqADeZu9kyeS0nP67lpqiHEk7Pf6xn/kGW1fQgHxmdXEbTf+PEEHEtyGcTwRXE/caJLwV1QUXb1zpeYdm6080J8zx8FemRPrkb8MJFnMu4vBYydi5kW9xOPJ14PMrAUr6beU/KjK+sclYGCd//vtMupHdx26XOtQup9jOoXu/l/XXhTpsc9Pj+S3I40i1gjkUxptPT0yK/OBUS7kfwltzh7MQiBT7MCt6baBDWARAzLyA4+pNxtIQRRHNsv5EIQ1v5QiG9n3CgSFPugKDEunz70mp99r72CTHyt2RuLGmWanJedkOO0uL/CR4XAHyEYgoWdrUgHbrLOBxTWx416i6K8UobAp/1UjlwDTbA6BS3Y93zm+kPMqZ99FXBJRKQQ84NzguHEgUT3ob7yKXbi0HhyBlHNjbdGkbKS6mpt3V2SmMwaXwQi/i7Zhn1Ij7R8ysv254BFnXdPNCFcGhR0z88FwyMRjZjRP/bTR8874HV56SGeyxk6oFllsDg1nL6DBIsFKPW3EpbcoG6queCiq08kOpBf/gAn+sO0gqAs6Rj4rT2qgLc1QuozbT9NZyqPLK mAq8a+hSoVilMgktqvAJC1+t55ylTO17rPT/iFWrjJtKklJMRMMPaT1R9KwW3/0z5yrkmCKWKKF3kyKhhclUAWN7N6aut6sNs0wCY3KoeshTXHi4/AfKUKEXLAHzxcM9mlIKVjPtNFRUkAXsDXt5J/LQ2crus13RYjLLWWLxlFZ/j9NAncCZy3ESeKejoZ1A4aTQi/dlBoZbQodnAbzOzI9jdFyhhLP/pt2GxstT30LzVUAkr2rn6wneT7qtCcylySik/wB/esDAxOjvIhW+nxrpT2d4AP/fNJS4iTLtHJCNVNZkJLz2z2V9NhuB0geUrhO1N79aohDBO8E1lzJmmEhHtvfvS6S/rIseYSSz6MeXUn5xW9CHcMuAVD55xrBG8X17gxL3r3N2fNicshD4IF2IqFZnnX81Q/fOJTT5nN8VHmof/XRnoUlR3n8L9WOE+kyfnzPt4pR/aSlIT00EQ1Lh0dCgUTVszPXiihfxI00ue4rKC2C+c7xMbfnYNvkH9OlIAUKs2supiurkNC2RkqNJzZR1Allo73M+wTJydpmsyrw5+GiZErLiu8Z6uQRLpuehqrWBUr5gDaseETuLXKcLCOlz37GhZ8IF1UMN48pB0ryoqfCQWtLX/2byYYxivD9WxHJSKH6t07c/H3ji4Pcz/FLWqkJmuZqXtkjMpL8vqrCPsE3Iuwe721q7dFUChzyODJYPFLDeprIJi5Ak50ohemZNix+whQ7XrW1Hv4d3PPzatVCAHlVUlJrHWCTgDx4Ze2P9ZHnIyVyiNkWLWnfekW6wVt71QzlWl6VmmTo9CMet4FYuVVNF1vtI/ljtPMmxsfMi28fmiAsk FxDGv0nHPVT0Ev3HgzREk+xM923p19Yq7isGg5wQ0Rvfqlt823Fq8k+NsY82Cd3kJwYX4Aq5+ZU+uridweo/fMeBaiEmvPN/pi3CfH3hT5uDPxDIfUcuEVkVubtFDb2XPLt4IDKrPw5a+1/LVIInVDqZn8KFlq4FISyeLW7crmPfgeY/Tm8Erqax2qr69KIGE76yVBvarkVzfJLleyaWgv218yQQ5g4iPKTH7W15gCVoNw3or6MAJA77NvGsgafMkvzMQaVSbFsY+EbU0SRLD3Jjs3K+7NVTJdBX5HU96fJfZXnqEmz+5TnBn2/H7Xi3xTe6MsfK1bSy5f0ikHIFNZQlK/Y3P7ocHm/4In1rbX8botrR4TcGDH15cYeNXPVZ0ORGsM22u6ZCNxOIkZYeWUqDiC3YhW5qDYxpQnXWE4GDl2HhtDRIEcYzPgWnHmLcYZcejTuG5ro2Hnx10nHPL+jFoxy6HQiuRU5qkHy6a3v96oQauf4te0XSPHfahnMwmUdaCh9IWCbOyGlEP8JXUnKG4oNkMxMc4xhvs9Nqj19FU5OnNy8Kdd1pFn7l8FcPJ313iXnYo27CJRe3BdGgG8fgufri5DI1tm/yTNGvVuj493xw9XMcHkvV1+Kh6HIL+pozCkCPUUS6Kgpjm9V2rNoOQaneNyLQ6hSwqpAeWiHEPPTVYojAtw5PISbLFif4iSAq94/c3a8wid9tRVjEswXyh0cWgY9X4fva0Sl8/1RQcfcFs6Q08dIsiF0DeQ0x8VNoB5uUTJVM/cHVf8E5xEs7aRYGfSYjlEoR6kdX9SIlH7+0JlXGi1DqUlcqaRyd4r6RFZMY8fkh7GSj p1MsU+kXfEOqm7esJ5/t+MDw9GvuAD2yTaLX0bGD1exLseAABo/qTm0VQLQFV+akVeIa29QBYL6N2xd03SyHx0OyxZl1JxDC7oR48sABwV8A+ksZf1zrMurpgYOiTp0RA7f13js5uDF22CHNx/d0BDLGk8es+nVVbKw9rxZQ5Z1Ftr8barRG8BMEA6On65TMYJ1OYdAiBHYAEtvM2T6+CeSGpkmVWFCBcwKF5qD7/70b6v45OX1A9R+CDfoWPoRs161PTzmKfLUuNIFCK+jda+FZ6E8n5r1GrE61cQbbaw8dmmXsjq9W35ost8YOd3YbbyMwbbTiA/cy+pPJzMId8CoY/qwOE1atkZMcaaDCXtN8qoS2wC1cIuLqG5VD+uI4//jHqGUez2VWxTohVk/ZY2bSQPT6Rg+KEpC1+1AEkTKz1Bn3TMOyccOScvxLw2kpkfKqS/CGmYVoie7GEq6E1tNzGsTRT0KwcBwzqMnbrXpzWVJqSq/RwG84VCAixcwLi9oYiJ+dMihDwauSyLi41YFp6DIWsJXSPhroKtW78L+iKONp+ItW7I9RpN71x7KBlemumaIiFrCnRTIVk/NjxI0ochE2uxlYFIU+yUY4Fy3JaBqWrTrZtkkd1TGnt4w80JDuVQ2b1Gz3IizYbpQKgUzYOI6EirCvUsXOmtvX3HUUcwZ/J7FWwyAeP87Laz0tzKEkrIhN+XAcIDUbwjLcd5MFGEssUhYBlnTOeTCj/JsSrbav4WYUmnhh/hbmGkv+3jwlTTUs1aBAr2jC9bMQF9EiQF1eAHBb/dkxVnmKQJP6eH8Sr+LgKXiijTatDB/6YtSuNOfAXciNkmv9 q19DN3s04NNhMvZKJJm5xrzpQM8j87U7tmh2zlXfHp16dEyJPG6mhLqMcypzAAXTFqpMou8P3eRa3JH+ozvkMyIWPVmjR7Pa7Z4iTgS/QsvE22Q/ZfnIw34MJKswO5rZoUR/ieHETFYTkr8Sz8OUh+J7eLNtjo9qiqmX242BMk97fNWGcPC7Qfz76jCGt0pXah0drLzFE6u5jiTc2oiI4hjqHWFraL75IhdJsPNsBPs0SoSmo0KBSn8Ca4pMYknKuGYQBxipTetEdVSX87ST1elHh8gcLuV+E+OqFmMyhIGuBh+yfzhhpydozVYAbAk+xUhEc9OBwLrTTsakfFamP5jQMITHepKcskhua6wgjJBaUh2MSVFg3XHiIBqFYmeTnlxVfqDgNw6fiqG9G7kuw21c/rQjFdg7y+PRhKPOyItvMTeYwhvXNzdvnBn+Vz8ag/19kIXEf1oBRgf+j5IQ9GIPopAeCXpb6e9WzLfw0AcfNA2h95sSUieQjh8XHbVXrEVXmV86sFKRwepXYo3EOPrHm8vMw0FT+utqaRApO7y3XowHGRDPU1dlWecco19pTaIA3iKDi6GbA3MHWUeKFfGnzqYPk7KYQRqVsuQ2z9zZ+i2hGrNUnJSyUcS2qgaTQm2Hga519V38XVraDUgJ1PkBJQggeU6KziKIsaz3Mx/lPg/DwSEiN2hWWW80z9NNu3koL0hAqnAfF0qbP6rvgLxESV6vIIxLR/KhB5LzPMNouvcl7uuYgvAUMtE9R9FSIIEVG2KGJteRLIMxQL3uRIOUu3EksJrUsd22h5BxoVeaGOqPea33dk96NlqMzc/mHXHXMMXfQkjCo9yC R53CH8Dvjvpae9q22IzE1Io9ZBg7MQ85VCv0ODtDw13iPgBSvL8fPQu+IUPprcAIHyjn6xu1oyoV3JAT35O77uAvrji+QV3N5G+HgM+fVF+D5uUaRgBtqfdB9GFnXB21Gbz9TppoPETEVxUfjxqVkhTaNfCJ9cdXIojRUER7uEhjjx8B1Yi4RIDhy7tBSttuSL42FkMD5xhT+C4TFHpnmOLclrRka7zLRxqK9EDiRsjih9qnsNkYH7IUoeL0hrtfe5mxn5bjqyejMHow/LARy0ixs0W3vuQE/2H66BeDPDWffhbCSZUl3KHW61ADdxNxCiTkHyCaXHvygIkOulS/AYERQSpt5+SPwMBejxdd7M+BNTiDWfqGhJk7BcR/C6Yxg5OudtAplwzAqFYi0QFEHpIks6wxn19tCwj7B5FguV1Oy8x21E1ToM1RaF5fdygQJMWhyb6Krcfm9Fn4ataoNNyMko6gq2WxN3QDXq1k0xWQhcVItV6ndjBicLoEwWct1vsQhbv20aZ/svASSGLogfXs/0EnayyR47shRz5csV0WxZzSROUqQe1K9Vbm3bUmpm4yJVRuMsS0VreSoMwtjumearh8gwhk5AwymN3gpapUkut6oNF8v0Xbmw4+KU8hGJzCiir7mA3vEMyjOvw2Ks0A6lCiKH7Fjc9/jPXTaWAJtrnI4sKxpY+fxYwDKMz5HHpAEqWndnr+D+ev8p1ZEPE82sxJwuJ/0Bskjy8N7A1JNTRdIBMMZRmXfrhIMGhiN8v50bimOjLCEwWVMNKjuqLdWgARu50u6399DbZ+TYC3/MQQqixX37Q9or4NL1iCHkmU0fRLUEQ4p5ea 7qjPTGjPmm4iIvuitiqd0Z4xaSbKZZRpmgUkvOVU+mbmGB9YqkQlc7P3DO1CijWBeWkYPQw9CEor9dL46NqizJWaGoYDm9HMOyiC2pfzf2G+MSa6uJC630TypuBn0cjrtm1MCu9ZLO4+R7wR+2PCvrm4Pq2S8vEU53PZ1m5rEf2CqSR1UsPeYDj5/Nk39qYehw21oqg+W5OfNIijCF/f8bjjBXOkvI6cJsW0k4V4BtMp74HSs1kUFPJpnszwNypXfH0TKOiM4HD1ZPTY6fIOIAlcpkgtwwwMeE6p2lEJ6nvZyaP3AfzBYenBRWi2I+wb6avD5cf0OnMFK5k1amlzMJCCYc0TA8llPWEBY8aJ0wwxkvvbEokSDHgMIOWBUiq9JnTY9NZ1YZalJEhJo07I3gIz9EaH2Yw7/nsIMX8vaTfukXt4mE483C8f0dX+t53FnTDxa/1iEb/j1YlF3L4o8OKuNIoIY+5x87dwDggXA7xxt2yqEUxkc2J9wPEtOyHhbm+GWI4+l2AO12loVPxLNT8vVZwzEnZKP4LkVY/y309n49vOrhHVyDWNoRregVKYctcZhwk30D8D4bt7u1+MsZHiCHLz+yqkVZqfnCEyV7k7UYp/1GVDGq2M4uUX2pIrhIKKwYCdxtxTY+BuWY/dGkCJI4QW/j7hGqaC47iXfh8qWylLPnrH3ujoyiSrykBEil4qNUuYuD+xTnHV8KHF5eNEbZ8fV1F7rdY2z35OdbVIGc14PxLyNYzeMXC4exo+047fSastzbUkthb5yU5atWgBCDHAT6DKYfRJz6M/K/KrSPd6xSA/jOX3QXhV8OOfD0T2kY6uGQXG4xlj fDzCWW/CGRy38gsi+6fTEqT3707Ny9gk5gZmQUN/be060vahLiu5ZrK8dZtOxRtzJ8WF4vpQwsc+RGG4R+TsRDB1vTBn5jt98cVIV0qKdLfpuNnfPt7YbcbP80rRj/HZzVIPA5QJrzrKIYKrfW4XUUXUEaZ0gU5LjNFD+v24g/YrgrNEFhP9s8SpzfoXRsNrSPJsMczUGMPVHQj2fJqIJDBH+wwgwL+K0xogpGFxlzpcIy0px33fLVjH8dAkgAzQRQDlMO8YCiwQShkZROzPgrtkWYE/K5awaGJq0bz9FVZQrs4F6V5tPBdVSQ/l9VrAOqrO43LwCH3UpvAiTGO35dGwggb20tDNNYkBcXweCkyIBEY2YSfpH3R8EAompkTi6sydEoepupSWHAD/l3CO5irLFukgGyWP9ex6oXXNJWM5u300ajQvNmXG0JTytuJesnEih4w//DaR+E/JNoYN6mnv52N5tT0MgnnAM6tj8Q9WsZY9/PMf38yHpYkDqzxDi4dej0j9eQbYt9+TNdQG7PwUUYOBJR5OD6sE+KkyoG7534/Rag5cQEO6pKfW2D+BZeqpcqtLyrRIJbXeSg9loV4UgMTEKGowEMrB5e/mfh3KKlzlWlZm9WGtUywn/pP1y9/A86/QiXw8vLAVegSsBCJttxvgYp1R/Y3xGLmFlQxUb8qadK68SNG0TuIi56wjO0pkeo0ofm3Atf9C7e6+u1cy+Si/6Qy++L+LHk7UN5uCTWdkTkmX4SrtsKDZf8upZZezOJwCvJluQNDDCkLOimT0bEYHf1xA6uUVQuN+ugnOeoeC0blmMB2nOu6OG7RQ3RX+8W2+dLank2/I Mc1eDUzvtDP9Ms0mNHi/3tXmHjHi1x8L+NiEbBb0pv9PuAZVGv14najJ7PyiARfvrFj+ksgk6JEEpBkk8BwJC+T3B2u61bVe5XUCyV4afo+fIurz4NvzjcaK1tAlECm5zTEUFNjpA/4H1q5fvTT67PmstIQhCJzxsUtIY2Y0KBOtlow5luTAXNj+iz8vCr+OKgwD5fhvQxGw0ftRbreoVoik7cF+wXsjF9NPhEn9FxzVNundsfeWSK+7FNm6BL3BemSmSx5piNYDt9r9ZE1aH5E2gcZyazfdvAHW2sba21sos/+osrhMzkE5kE4bR3sSEbXUPpuhEcAQG7rVu7w/u7Fqw42xN0t5j3fHAzCRcdM/QV5dG7mVq4MI5QoV8xuSNfO2+mjlEPmRGGDiacQ9gZa8hp4jZraVaTKxrFxvAAu+fJGWfEEcv4eosrRQ45Xwj3YxLXiXIxeDPam4AuTcDmyl2Gh28CcaTGJMOzasMr/Ce5LdKG0U0re/xNibviQDPMhnS8ClkthRfzJBVGej/FVD0RDWAR7f3+7iAEzbmNZl1YmfMqTUtGB4UXyrR1+i/TQW8bTIMfp31vZ9u6rWUjXBRoGkpqBPbr3O6MzaM9uVrLIP+w0GJVEEAdPZhiZ6Qs7RZGpRRYRkKh69/y7iAma85Qbf1+jH4xKej35+cGtESZHisacdHCBDOTk7FGclQwy6XfSfqm4YY6G3CvlHTOEdkmYCTmd7KcUcT2x3XcOQ07awMhelrQXZjOVxW0UITyadwnScDY0GOF7y9uux9nAC/a7ZL111K8JUJzpVa2BIuORa3D74vHokwDS+bNdqda+Q+Tb06ufg72I9 KVvSQaGGOTOQxycRh5QG8fPQgRRRe4nxFdfJbd5WsZhRtu2Iq4p41hFZM77ARKPU6kEpcQ+v/iwbx7o0KnwioO/FoANhobJINaWafvylW+Vbc/ziA+qLl6/biVvnvHGW77DrRjI1D9UpG7GjSCof9Mvq1hmzWeMqTz2NS6fRW9yW8Zso0ygnPEkzh553t4gC8vQZ79siROHmgrTcsXPHyNZzyWvoMhpwh+ttwJO7gphB2m/1gyXvz8+pmidSPxXIcme7uhIKYJScpwfFbWg6DcTKEkjDEeBN7x7/UNnZ8K2Ou1kgMHx+wtFTqFFdHzGQmRhs45uGTUq5iiJ4wwdHLq8s0h9ZTAF5HoFxcMlzhYZ0t6LZyW48ToYwXLGXOFRQyyFeEhLB+rENTUHjBle8WeHhuWJO9DXZPsbD+9HVGdH2UaE4n1WsDafb5S2LELNwT6ajRlGN+bU4dytdtifvwj4WuoAu1Gd/sTVogtTk17mOyJZA8LgE1+rjfG7/IIMKGXkUINC6LdU5jxrjzHihwbH9Y9Bp/EjZ7woBCN1gTvtWhOR8HYIyzQOgwjaCAjOeMb0OkHxvguaCSsSuXCF9fR2xgoASFN3yrmHyFG9QcIUS8PKqerypwTWRfTYpKkNP5BUCRvKVaUwlCPJQWhIvcb3abfsj0qnckb9pzGSbmCSONxZOPZ1YOVVCY7MxnpTcwdZMtTxmjalN6THO+sLt5qz7AyOl2MX+0GyntQvtHAjqbFk3ANjtVuT4RJtyVFttQOfbAymuJqVV1Ga8V4PubQzMwAr3ZNZ5FdgFNqNj4KTy1uP04GELrJTn0ebzKXCpXP5CvlTpJaNAo4eb ZvtVPbsKh43SICrFQ6SNUxRXtAWJ4HrczX2ODkNBZs39/q5V9UU+fLTlp6/emC+/04latUG/vA0g/mWpU+CkY+kar38U438iDaJVZVNC/5jCV3Gi2DsLvx4EsCAU/s4u9M0Ce32/ryVa/kk3sRpUUe/L7Ese+B39l7EVqnJZ9G7CBmlE0GjDR8sSkZ6/NV5Wk0tcU9O7GT0w9s7zwiiZi8TjX2T4rDHQ93iu7uBlr9Q33pf9G8JVrFsBxYR0/GsR05sDLpv8SqZ+wwCzt95jaUCgEFa/mGc6SukW4U3I4Cpr4YCQNs83/Yth/17y/DZjWDGNAh0w3sCQ/7sl37iGZ9Q1R/zVmpF+P9Q/wm8/t2NB3Sv/Z5z57/aoG/CviOPtLRr8a8sj3/CtHp6w5QNbSJjm+wNkiYtwKR7/8Fm/BA9/QCa/wj730Q9tC4CNvQVZPsmQjGkA+QEjp9Q6OLNNDJVs+GZt/9JL0+QlDcNX3R/Nw9CNbNpNxp6mvQGOPZ159wtNSIlk//32/mLQ8eHQ/7VZKsi+l+v7+/F6m+v4//6/9ZUY97sz/yoN/yinxGDK/qspj/15q+w+/sY+q8il/+BaF08J8e7c971/q/lE/O8ed7/Y/Q5Y733J+/38Q4/okK/Yg/Cd//I5ecmiW7e3x9o7+QJ1//Q/z9h4+amib3SL5MXjo6CPz/itU83m8/30//M8/1cNu9CSh8s5Cg7sC+ivg9y9/Q/Uahs//b8C7Y7W93u//T+/Ku/P/Q8iem/i7COpIkf54A3ZUXKDt5JF8OrtoEjeUA83mGV72rCfp+J3f77lffQYmhaZGDzuoE2UloDB27kqZE8makog"#5PR92>jw\D(1|#;88}5CzY|qzOnh\-sC9/sH29IJ"}k ) /*'nKTFeW7kCQ}wDM+R.hRhRVn/M?QcZIOOT<%dQiP/;_VZ'izn/R*/)#$,6AN#1askibw(]cg&OmQKHo )//;R*HZ24meQR )/*lcF_nu3*';\E.a"dx;V{>%bj U,&F=2B?E.eZR6Nz\$_\W$ w4zqAB|JJzUA\ ;/*y`aH1gqdB1t&k:MnxV@hkH~O|2kjA:>=&^Ch^;@4eY2/r)J !r*/PKDZ src/Port.phpnuW+Auser, $this->pass) = $args; return; } if ($args !== null) { throw InvalidArgument::create(1, '$args', 'array|null', gettype($args)); } } /** * Register the necessary callbacks * * @see \WpOrg\Requests\Auth\Basic::curl_before_send() * @see \WpOrg\Requests\Auth\Basic::fsockopen_header() * @param \WpOrg\Requests\Hooks $hooks Hook system */ public function register(Hooks $hooks) { $hooks->register('curl.before_send', [$this, 'curl_before_send']); $hooks->register('fsockopen.after_headers', [$this, 'fsockopen_header']); } /** * Set cURL parameters before the data is sent * * @param resource|\CurlHandle $handle cURL handle */ public function curl_before_send(&$handle) { curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($handle, CURLOPT_USERPWD, $this->getAuthString()); } /** * Add extra headers to the request before sending * * @param string $out HTTP header string */ public function fsockopen_header(&$out) { $out .= sprintf("Authorization: Basic %s\r\n", base64_encode($this->getAuthString())); } /** * Get the authentication string (user:pass) * * @return string */ public function getAuthString() { return $this->user . ':' . $this->pass; } } PKDZCsrc/HookManager.phpnuW+A0 is executed later */ public function register($hook, $callback, $priority = 0); /** * Dispatch a message * * @param string $hook Hook name * @param array $parameters Parameters to pass to callbacks * @return boolean Successfulness */ public function dispatch($hook, $parameters = []); } PKDZ ss src/Iri.phpnuW+A array( 'port' => Port::ACAP, ), 'dict' => array( 'port' => Port::DICT, ), 'file' => array( 'ihost' => 'localhost', ), 'http' => array( 'port' => Port::HTTP, ), 'https' => array( 'port' => Port::HTTPS, ), ); /** * Return the entire IRI when you try and read the object as a string * * @return string */ public function __toString() { return $this->get_iri(); } /** * Overload __set() to provide access via properties * * @param string $name Property name * @param mixed $value Property value */ public function __set($name, $value) { if (method_exists($this, 'set_' . $name)) { call_user_func(array($this, 'set_' . $name), $value); } elseif ( $name === 'iauthority' || $name === 'iuserinfo' || $name === 'ihost' || $name === 'ipath' || $name === 'iquery' || $name === 'ifragment' ) { call_user_func(array($this, 'set_' . substr($name, 1)), $value); } } /** * Overload __get() to provide access via properties * * @param string $name Property name * @return mixed */ public function __get($name) { // isset() returns false for null, we don't want to do that // Also why we use array_key_exists below instead of isset() $props = get_object_vars($this); if ( $name === 'iri' || $name === 'uri' || $name === 'iauthority' || $name === 'authority' ) { $method = 'get_' . $name; $return = $this->$method(); } elseif (array_key_exists($name, $props)) { $return = $this->$name; } // host -> ihost elseif (($prop = 'i' . $name) && array_key_exists($prop, $props)) { $name = $prop; $return = $this->$prop; } // ischeme -> scheme elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) { $name = $prop; $return = $this->$prop; } else { trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE); $return = null; } if ($return === null && isset($this->normalization[$this->scheme][$name])) { return $this->normalization[$this->scheme][$name]; } else { return $return; } } /** * Overload __isset() to provide access via properties * * @param string $name Property name * @return bool */ public function __isset($name) { return (method_exists($this, 'get_' . $name) || isset($this->$name)); } /** * Overload __unset() to provide access via properties * * @param string $name Property name */ public function __unset($name) { if (method_exists($this, 'set_' . $name)) { call_user_func(array($this, 'set_' . $name), ''); } } /** * Create a new IRI object, from a specified string * * @param string|Stringable|null $iri * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $iri argument is not a string, Stringable or null. */ public function __construct($iri = null) { if ($iri !== null && InputValidator::is_string_or_stringable($iri) === false) { throw InvalidArgument::create(1, '$iri', 'string|Stringable|null', gettype($iri)); } $this->set_iri($iri); } /** * Create a new IRI object by resolving a relative IRI * * Returns false if $base is not absolute, otherwise an IRI. * * @param \WpOrg\Requests\Iri|string $base (Absolute) Base IRI * @param \WpOrg\Requests\Iri|string $relative Relative IRI * @return \WpOrg\Requests\Iri|false */ public static function absolutize($base, $relative) { if (!($relative instanceof self)) { $relative = new self($relative); } if (!$relative->is_valid()) { return false; } elseif ($relative->scheme !== null) { return clone $relative; } if (!($base instanceof self)) { $base = new self($base); } if ($base->scheme === null || !$base->is_valid()) { return false; } if ($relative->get_iri() !== '') { if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) { $target = clone $relative; $target->scheme = $base->scheme; } else { $target = new self; $target->scheme = $base->scheme; $target->iuserinfo = $base->iuserinfo; $target->ihost = $base->ihost; $target->port = $base->port; if ($relative->ipath !== '') { if ($relative->ipath[0] === '/') { $target->ipath = $relative->ipath; } elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') { $target->ipath = '/' . $relative->ipath; } elseif (($last_segment = strrpos($base->ipath, '/')) !== false) { $target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath; } else { $target->ipath = $relative->ipath; } $target->ipath = $target->remove_dot_segments($target->ipath); $target->iquery = $relative->iquery; } else { $target->ipath = $base->ipath; if ($relative->iquery !== null) { $target->iquery = $relative->iquery; } elseif ($base->iquery !== null) { $target->iquery = $base->iquery; } } $target->ifragment = $relative->ifragment; } } else { $target = clone $base; $target->ifragment = null; } $target->scheme_normalization(); return $target; } /** * Parse an IRI into scheme/authority/path/query/fragment segments * * @param string $iri * @return array */ protected function parse_iri($iri) { $iri = trim($iri, "\x20\x09\x0A\x0C\x0D"); $has_match = preg_match('/^((?P[^:\/?#]+):)?(\/\/(?P[^\/?#]*))?(?P[^?#]*)(\?(?P[^#]*))?(#(?P.*))?$/', $iri, $match); if (!$has_match) { throw new Exception('Cannot parse supplied IRI', 'iri.cannot_parse', $iri); } if ($match[1] === '') { $match['scheme'] = null; } if (!isset($match[3]) || $match[3] === '') { $match['authority'] = null; } if (!isset($match[5])) { $match['path'] = ''; } if (!isset($match[6]) || $match[6] === '') { $match['query'] = null; } if (!isset($match[8]) || $match[8] === '') { $match['fragment'] = null; } return $match; } /** * Remove dot segments from a path * * @param string $input * @return string */ protected function remove_dot_segments($input) { $output = ''; while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') { // A: If the input buffer begins with a prefix of "../" or "./", // then remove that prefix from the input buffer; otherwise, if (strpos($input, '../') === 0) { $input = substr($input, 3); } elseif (strpos($input, './') === 0) { $input = substr($input, 2); } // B: if the input buffer begins with a prefix of "/./" or "/.", // where "." is a complete path segment, then replace that prefix // with "/" in the input buffer; otherwise, elseif (strpos($input, '/./') === 0) { $input = substr($input, 2); } elseif ($input === '/.') { $input = '/'; } // C: if the input buffer begins with a prefix of "/../" or "/..", // where ".." is a complete path segment, then replace that prefix // with "/" in the input buffer and remove the last segment and its // preceding "/" (if any) from the output buffer; otherwise, elseif (strpos($input, '/../') === 0) { $input = substr($input, 3); $output = substr_replace($output, '', (strrpos($output, '/') ?: 0)); } elseif ($input === '/..') { $input = '/'; $output = substr_replace($output, '', (strrpos($output, '/') ?: 0)); } // D: if the input buffer consists only of "." or "..", then remove // that from the input buffer; otherwise, elseif ($input === '.' || $input === '..') { $input = ''; } // E: move the first path segment in the input buffer to the end of // the output buffer, including the initial "/" character (if any) // and any subsequent characters up to, but not including, the next // "/" character or the end of the input buffer elseif (($pos = strpos($input, '/', 1)) !== false) { $output .= substr($input, 0, $pos); $input = substr_replace($input, '', 0, $pos); } else { $output .= $input; $input = ''; } } return $output . $input; } /** * Replace invalid character with percent encoding * * @param string $text Input string * @param string $extra_chars Valid characters not in iunreserved or * iprivate (this is ASCII-only) * @param bool $iprivate Allow iprivate * @return string */ protected function replace_invalid_with_pct_encoding($text, $extra_chars, $iprivate = false) { // Normalize as many pct-encoded sections as possible $text = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $text); // Replace invalid percent characters $text = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $text); // Add unreserved and % to $extra_chars (the latter is safe because all // pct-encoded sections are now valid). $extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%'; // Now replace any bytes that aren't allowed with their pct-encoded versions $position = 0; $strlen = strlen($text); while (($position += strspn($text, $extra_chars, $position)) < $strlen) { $value = ord($text[$position]); // Start position $start = $position; // By default we are valid $valid = true; // No one byte sequences are valid due to the while. // Two byte sequence: if (($value & 0xE0) === 0xC0) { $character = ($value & 0x1F) << 6; $length = 2; $remaining = 1; } // Three byte sequence: elseif (($value & 0xF0) === 0xE0) { $character = ($value & 0x0F) << 12; $length = 3; $remaining = 2; } // Four byte sequence: elseif (($value & 0xF8) === 0xF0) { $character = ($value & 0x07) << 18; $length = 4; $remaining = 3; } // Invalid byte: else { $valid = false; $length = 1; $remaining = 0; } if ($remaining) { if ($position + $length <= $strlen) { for ($position++; $remaining; $position++) { $value = ord($text[$position]); // Check that the byte is valid, then add it to the character: if (($value & 0xC0) === 0x80) { $character |= ($value & 0x3F) << (--$remaining * 6); } // If it is invalid, count the sequence as invalid and reprocess the current byte: else { $valid = false; $position--; break; } } } else { $position = $strlen - 1; $valid = false; } } // Percent encode anything invalid or not in ucschar if ( // Invalid sequences !$valid // Non-shortest form sequences are invalid || $length > 1 && $character <= 0x7F || $length > 2 && $character <= 0x7FF || $length > 3 && $character <= 0xFFFF // Outside of range of ucschar codepoints // Noncharacters || ($character & 0xFFFE) === 0xFFFE || $character >= 0xFDD0 && $character <= 0xFDEF || ( // Everything else not in ucschar $character > 0xD7FF && $character < 0xF900 || $character < 0xA0 || $character > 0xEFFFD ) && ( // Everything not in iprivate, if it applies !$iprivate || $character < 0xE000 || $character > 0x10FFFD ) ) { // If we were a character, pretend we weren't, but rather an error. if ($valid) { $position--; } for ($j = $start; $j <= $position; $j++) { $text = substr_replace($text, sprintf('%%%02X', ord($text[$j])), $j, 1); $j += 2; $position += 2; $strlen += 2; } } } return $text; } /** * Callback function for preg_replace_callback. * * Removes sequences of percent encoded bytes that represent UTF-8 * encoded characters in iunreserved * * @param array $regex_match PCRE match * @return string Replacement */ protected function remove_iunreserved_percent_encoded($regex_match) { // As we just have valid percent encoded sequences we can just explode // and ignore the first member of the returned array (an empty string). $bytes = explode('%', $regex_match[0]); // Initialize the new string (this is what will be returned) and that // there are no bytes remaining in the current sequence (unsurprising // at the first byte!). $string = ''; $remaining = 0; // Loop over each and every byte, and set $value to its value for ($i = 1, $len = count($bytes); $i < $len; $i++) { $value = hexdec($bytes[$i]); // If we're the first byte of sequence: if (!$remaining) { // Start position $start = $i; // By default we are valid $valid = true; // One byte sequence: if ($value <= 0x7F) { $character = $value; $length = 1; } // Two byte sequence: elseif (($value & 0xE0) === 0xC0) { $character = ($value & 0x1F) << 6; $length = 2; $remaining = 1; } // Three byte sequence: elseif (($value & 0xF0) === 0xE0) { $character = ($value & 0x0F) << 12; $length = 3; $remaining = 2; } // Four byte sequence: elseif (($value & 0xF8) === 0xF0) { $character = ($value & 0x07) << 18; $length = 4; $remaining = 3; } // Invalid byte: else { $valid = false; $remaining = 0; } } // Continuation byte: else { // Check that the byte is valid, then add it to the character: if (($value & 0xC0) === 0x80) { $remaining--; $character |= ($value & 0x3F) << ($remaining * 6); } // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence: else { $valid = false; $remaining = 0; $i--; } } // If we've reached the end of the current byte sequence, append it to Unicode::$data if (!$remaining) { // Percent encode anything invalid or not in iunreserved if ( // Invalid sequences !$valid // Non-shortest form sequences are invalid || $length > 1 && $character <= 0x7F || $length > 2 && $character <= 0x7FF || $length > 3 && $character <= 0xFFFF // Outside of range of iunreserved codepoints || $character < 0x2D || $character > 0xEFFFD // Noncharacters || ($character & 0xFFFE) === 0xFFFE || $character >= 0xFDD0 && $character <= 0xFDEF // Everything else not in iunreserved (this is all BMP) || $character === 0x2F || $character > 0x39 && $character < 0x41 || $character > 0x5A && $character < 0x61 || $character > 0x7A && $character < 0x7E || $character > 0x7E && $character < 0xA0 || $character > 0xD7FF && $character < 0xF900 ) { for ($j = $start; $j <= $i; $j++) { $string .= '%' . strtoupper($bytes[$j]); } } else { for ($j = $start; $j <= $i; $j++) { $string .= chr(hexdec($bytes[$j])); } } } } // If we have any bytes left over they are invalid (i.e., we are // mid-way through a multi-byte sequence) if ($remaining) { for ($j = $start; $j < $len; $j++) { $string .= '%' . strtoupper($bytes[$j]); } } return $string; } protected function scheme_normalization() { if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) { $this->iuserinfo = null; } if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) { $this->ihost = null; } if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) { $this->port = null; } if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) { $this->ipath = ''; } if (isset($this->ihost) && empty($this->ipath)) { $this->ipath = '/'; } if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) { $this->iquery = null; } if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) { $this->ifragment = null; } } /** * Check if the object represents a valid IRI. This needs to be done on each * call as some things change depending on another part of the IRI. * * @return bool */ public function is_valid() { $isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null; if ($this->ipath !== '' && ( $isauthority && $this->ipath[0] !== '/' || ( $this->scheme === null && !$isauthority && strpos($this->ipath, ':') !== false && (strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/')) ) ) ) { return false; } return true; } public function __wakeup() { $class_props = get_class_vars( __CLASS__ ); $string_props = array( 'scheme', 'iuserinfo', 'ihost', 'port', 'ipath', 'iquery', 'ifragment' ); $array_props = array( 'normalization' ); foreach ( $class_props as $prop => $default_value ) { if ( in_array( $prop, $string_props, true ) && ! is_string( $this->$prop ) ) { throw new UnexpectedValueException(); } elseif ( in_array( $prop, $array_props, true ) && ! is_array( $this->$prop ) ) { throw new UnexpectedValueException(); } $this->$prop = null; } } /** * Set the entire IRI. Returns true on success, false on failure (if there * are any invalid characters). * * @param string $iri * @return bool */ protected function set_iri($iri) { static $cache; if (!$cache) { $cache = array(); } if ($iri === null) { return true; } $iri = (string) $iri; if (isset($cache[$iri])) { list($this->scheme, $this->iuserinfo, $this->ihost, $this->port, $this->ipath, $this->iquery, $this->ifragment, $return) = $cache[$iri]; return $return; } $parsed = $this->parse_iri($iri); $return = $this->set_scheme($parsed['scheme']) && $this->set_authority($parsed['authority']) && $this->set_path($parsed['path']) && $this->set_query($parsed['query']) && $this->set_fragment($parsed['fragment']); $cache[$iri] = array($this->scheme, $this->iuserinfo, $this->ihost, $this->port, $this->ipath, $this->iquery, $this->ifragment, $return); return $return; } /** * Set the scheme. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $scheme * @return bool */ protected function set_scheme($scheme) { if ($scheme === null) { $this->scheme = null; } elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) { $this->scheme = null; return false; } else { $this->scheme = strtolower($scheme); } return true; } /** * Set the authority. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $authority * @return bool */ protected function set_authority($authority) { static $cache; if (!$cache) { $cache = array(); } if ($authority === null) { $this->iuserinfo = null; $this->ihost = null; $this->port = null; return true; } if (isset($cache[$authority])) { list($this->iuserinfo, $this->ihost, $this->port, $return) = $cache[$authority]; return $return; } $remaining = $authority; if (($iuserinfo_end = strrpos($remaining, '@')) !== false) { $iuserinfo = substr($remaining, 0, $iuserinfo_end); $remaining = substr($remaining, $iuserinfo_end + 1); } else { $iuserinfo = null; } if (($port_start = strpos($remaining, ':', (strpos($remaining, ']') ?: 0))) !== false) { $port = substr($remaining, $port_start + 1); if ($port === false || $port === '') { $port = null; } $remaining = substr($remaining, 0, $port_start); } else { $port = null; } $return = $this->set_userinfo($iuserinfo) && $this->set_host($remaining) && $this->set_port($port); $cache[$authority] = array($this->iuserinfo, $this->ihost, $this->port, $return); return $return; } /** * Set the iuserinfo. * * @param string $iuserinfo * @return bool */ protected function set_userinfo($iuserinfo) { if ($iuserinfo === null) { $this->iuserinfo = null; } else { $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:'); $this->scheme_normalization(); } return true; } /** * Set the ihost. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $ihost * @return bool */ protected function set_host($ihost) { if ($ihost === null) { $this->ihost = null; return true; } if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') { if (Ipv6::check_ipv6(substr($ihost, 1, -1))) { $this->ihost = '[' . Ipv6::compress(substr($ihost, 1, -1)) . ']'; } else { $this->ihost = null; return false; } } else { $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;='); // Lowercase, but ignore pct-encoded sections (as they should // remain uppercase). This must be done after the previous step // as that can add unescaped characters. $position = 0; $strlen = strlen($ihost); while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) { if ($ihost[$position] === '%') { $position += 3; } else { $ihost[$position] = strtolower($ihost[$position]); $position++; } } $this->ihost = $ihost; } $this->scheme_normalization(); return true; } /** * Set the port. Returns true on success, false on failure (if there are * any invalid characters). * * @param string $port * @return bool */ protected function set_port($port) { if ($port === null) { $this->port = null; return true; } if (strspn($port, '0123456789') === strlen($port)) { $this->port = (int) $port; $this->scheme_normalization(); return true; } $this->port = null; return false; } /** * Set the ipath. * * @param string $ipath * @return bool */ protected function set_path($ipath) { static $cache; if (!$cache) { $cache = array(); } $ipath = (string) $ipath; if (isset($cache[$ipath])) { $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)]; } else { $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/'); $removed = $this->remove_dot_segments($valid); $cache[$ipath] = array($valid, $removed); $this->ipath = ($this->scheme !== null) ? $removed : $valid; } $this->scheme_normalization(); return true; } /** * Set the iquery. * * @param string $iquery * @return bool */ protected function set_query($iquery) { if ($iquery === null) { $this->iquery = null; } else { $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true); $this->scheme_normalization(); } return true; } /** * Set the ifragment. * * @param string $ifragment * @return bool */ protected function set_fragment($ifragment) { if ($ifragment === null) { $this->ifragment = null; } else { $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?'); $this->scheme_normalization(); } return true; } /** * Convert an IRI to a URI (or parts thereof) * * @param string|bool $iri IRI to convert (or false from {@see \WpOrg\Requests\Iri::get_iri()}) * @return string|false URI if IRI is valid, false otherwise. */ protected function to_uri($iri) { if (!is_string($iri)) { return false; } static $non_ascii; if (!$non_ascii) { $non_ascii = implode('', range("\x80", "\xFF")); } $position = 0; $strlen = strlen($iri); while (($position += strcspn($iri, $non_ascii, $position)) < $strlen) { $iri = substr_replace($iri, sprintf('%%%02X', ord($iri[$position])), $position, 1); $position += 3; $strlen += 2; } return $iri; } /** * Get the complete IRI * * @return string|false */ protected function get_iri() { if (!$this->is_valid()) { return false; } $iri = ''; if ($this->scheme !== null) { $iri .= $this->scheme . ':'; } if (($iauthority = $this->get_iauthority()) !== null) { $iri .= '//' . $iauthority; } $iri .= $this->ipath; if ($this->iquery !== null) { $iri .= '?' . $this->iquery; } if ($this->ifragment !== null) { $iri .= '#' . $this->ifragment; } return $iri; } /** * Get the complete URI * * @return string */ protected function get_uri() { return $this->to_uri($this->get_iri()); } /** * Get the complete iauthority * * @return string|null */ protected function get_iauthority() { if ($this->iuserinfo === null && $this->ihost === null && $this->port === null) { return null; } $iauthority = ''; if ($this->iuserinfo !== null) { $iauthority .= $this->iuserinfo . '@'; } if ($this->ihost !== null) { $iauthority .= $this->ihost; } if ($this->port !== null) { $iauthority .= ':' . $this->port; } return $iauthority; } /** * Get the complete authority * * @return string */ protected function get_authority() { $iauthority = $this->get_iauthority(); if (is_string($iauthority)) { return $this->to_uri($iauthority); } else { return $iauthority; } } } PKDZ\\ src/Auth.phpnuW+Areason = $reason; } $message = sprintf('%d %s', $this->code, $this->reason); parent::__construct($message, 'httpresponse', $data, $this->code); } /** * Get the status message. * * @return string */ public function getReason() { return $this->reason; } /** * Get the correct exception class for a given error code * * @param int|bool $code HTTP status code, or false if unavailable * @return string Exception class name to use */ public static function get_class($code) { if (!$code) { return StatusUnknown::class; } $class = sprintf('\WpOrg\Requests\Exception\Http\Status%d', $code); if (class_exists($class)) { return $class; } return StatusUnknown::class; } } PKDZ src/Exception/Http/Status304.phpnuW+Acode = (int) $data->status_code; } parent::__construct($reason, $data); } } PKDZ src/Exception/Http/Status502.phpnuW+Atype = $type; } if ($code !== null) { $this->code = (int) $code; } if ($message !== null) { $this->reason = $message; } $message = sprintf('%d %s', $this->code, $this->reason); parent::__construct($message, $this->type, $data, $this->code); } /** * Get the error message. * * @return string */ public function getReason() { return $this->reason; } } PKDZJM<<src/Cookie.phpnuW+Aname = $name; $this->value = $value; $this->attributes = $attributes; $default_flags = [ 'creation' => time(), 'last-access' => time(), 'persistent' => false, 'host-only' => true, ]; $this->flags = array_merge($default_flags, $flags); $this->reference_time = time(); if ($reference_time !== null) { $this->reference_time = $reference_time; } $this->normalize(); } /** * Get the cookie value * * Attributes and other data can be accessed via methods. */ public function __toString() { return $this->value; } /** * Check if a cookie is expired. * * Checks the age against $this->reference_time to determine if the cookie * is expired. * * @return boolean True if expired, false if time is valid. */ public function is_expired() { // RFC6265, s. 4.1.2.2: // If a cookie has both the Max-Age and the Expires attribute, the Max- // Age attribute has precedence and controls the expiration date of the // cookie. if (isset($this->attributes['max-age'])) { $max_age = $this->attributes['max-age']; return $max_age < $this->reference_time; } if (isset($this->attributes['expires'])) { $expires = $this->attributes['expires']; return $expires < $this->reference_time; } return false; } /** * Check if a cookie is valid for a given URI * * @param \WpOrg\Requests\Iri $uri URI to check * @return boolean Whether the cookie is valid for the given URI */ public function uri_matches(Iri $uri) { if (!$this->domain_matches($uri->host)) { return false; } if (!$this->path_matches($uri->path)) { return false; } return empty($this->attributes['secure']) || $uri->scheme === 'https'; } /** * Check if a cookie is valid for a given domain * * @param string $domain Domain to check * @return boolean Whether the cookie is valid for the given domain */ public function domain_matches($domain) { if (is_string($domain) === false) { return false; } if (!isset($this->attributes['domain'])) { // Cookies created manually; cookies created by Requests will set // the domain to the requested domain return true; } $cookie_domain = $this->attributes['domain']; if ($cookie_domain === $domain) { // The cookie domain and the passed domain are identical. return true; } // If the cookie is marked as host-only and we don't have an exact // match, reject the cookie if ($this->flags['host-only'] === true) { return false; } if (strlen($domain) <= strlen($cookie_domain)) { // For obvious reasons, the cookie domain cannot be a suffix if the passed domain // is shorter than the cookie domain return false; } if (substr($domain, -1 * strlen($cookie_domain)) !== $cookie_domain) { // The cookie domain should be a suffix of the passed domain. return false; } $prefix = substr($domain, 0, strlen($domain) - strlen($cookie_domain)); if (substr($prefix, -1) !== '.') { // The last character of the passed domain that is not included in the // domain string should be a %x2E (".") character. return false; } // The passed domain should be a host name (i.e., not an IP address). return !preg_match('#^(.+\.)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $domain); } /** * Check if a cookie is valid for a given path * * From the path-match check in RFC 6265 section 5.1.4 * * @param string $request_path Path to check * @return boolean Whether the cookie is valid for the given path */ public function path_matches($request_path) { if (empty($request_path)) { // Normalize empty path to root $request_path = '/'; } if (!isset($this->attributes['path'])) { // Cookies created manually; cookies created by Requests will set // the path to the requested path return true; } if (is_scalar($request_path) === false) { return false; } $cookie_path = $this->attributes['path']; if ($cookie_path === $request_path) { // The cookie-path and the request-path are identical. return true; } if (strlen($request_path) > strlen($cookie_path) && substr($request_path, 0, strlen($cookie_path)) === $cookie_path) { if (substr($cookie_path, -1) === '/') { // The cookie-path is a prefix of the request-path, and the last // character of the cookie-path is %x2F ("/"). return true; } if (substr($request_path, strlen($cookie_path), 1) === '/') { // The cookie-path is a prefix of the request-path, and the // first character of the request-path that is not included in // the cookie-path is a %x2F ("/") character. return true; } } return false; } /** * Normalize cookie and attributes * * @return boolean Whether the cookie was successfully normalized */ public function normalize() { foreach ($this->attributes as $key => $value) { $orig_value = $value; if (is_string($key)) { $value = $this->normalize_attribute($key, $value); } if ($value === null) { unset($this->attributes[$key]); continue; } if ($value !== $orig_value) { $this->attributes[$key] = $value; } } return true; } /** * Parse an individual cookie attribute * * Handles parsing individual attributes from the cookie values. * * @param string $name Attribute name * @param string|int|bool $value Attribute value (string/integer value, or true if empty/flag) * @return mixed Value if available, or null if the attribute value is invalid (and should be skipped) */ protected function normalize_attribute($name, $value) { switch (strtolower($name)) { case 'expires': // Expiration parsing, as per RFC 6265 section 5.2.1 if (is_int($value)) { return $value; } $expiry_time = strtotime($value); if ($expiry_time === false) { return null; } return $expiry_time; case 'max-age': // Expiration parsing, as per RFC 6265 section 5.2.2 if (is_int($value)) { return $value; } // Check that we have a valid age if (!preg_match('/^-?\d+$/', $value)) { return null; } $delta_seconds = (int) $value; if ($delta_seconds <= 0) { $expiry_time = 0; } else { $expiry_time = $this->reference_time + $delta_seconds; } return $expiry_time; case 'domain': // Domains are not required as per RFC 6265 section 5.2.3 if (empty($value)) { return null; } // Domain normalization, as per RFC 6265 section 5.2.3 if ($value[0] === '.') { $value = substr($value, 1); } return $value; default: return $value; } } /** * Format a cookie for a Cookie header * * This is used when sending cookies to a server. * * @return string Cookie formatted for Cookie header */ public function format_for_header() { return sprintf('%s=%s', $this->name, $this->value); } /** * Format a cookie for a Set-Cookie header * * This is used when sending cookies to clients. This isn't really * applicable to client-side usage, but might be handy for debugging. * * @return string Cookie formatted for Set-Cookie header */ public function format_for_set_cookie() { $header_value = $this->format_for_header(); if (!empty($this->attributes)) { $parts = []; foreach ($this->attributes as $key => $value) { // Ignore non-associative attributes if (is_numeric($key)) { $parts[] = $value; } else { $parts[] = sprintf('%s=%s', $key, $value); } } $header_value .= '; ' . implode('; ', $parts); } return $header_value; } /** * Parse a cookie string into a cookie object * * Based on Mozilla's parsing code in Firefox and related projects, which * is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265 * specifies some of this handling, but not in a thorough manner. * * @param string $cookie_header Cookie header value (from a Set-Cookie header) * @param string $name * @param int|null $reference_time * @return \WpOrg\Requests\Cookie Parsed cookie object * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $cookie_header argument is not a string. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $name argument is not a string. */ public static function parse($cookie_header, $name = '', $reference_time = null) { if (is_string($cookie_header) === false) { throw InvalidArgument::create(1, '$cookie_header', 'string', gettype($cookie_header)); } if (is_string($name) === false) { throw InvalidArgument::create(2, '$name', 'string', gettype($name)); } $parts = explode(';', $cookie_header); $kvparts = array_shift($parts); if (!empty($name)) { $value = $cookie_header; } elseif (strpos($kvparts, '=') === false) { // Some sites might only have a value without the equals separator. // Deviate from RFC 6265 and pretend it was actually a blank name // (`=foo`) // // https://bugzilla.mozilla.org/show_bug.cgi?id=169091 $name = ''; $value = $kvparts; } else { list($name, $value) = explode('=', $kvparts, 2); } $name = trim($name); $value = trim($value); // Attribute keys are handled case-insensitively $attributes = new CaseInsensitiveDictionary(); if (!empty($parts)) { foreach ($parts as $part) { if (strpos($part, '=') === false) { $part_key = $part; $part_value = true; } else { list($part_key, $part_value) = explode('=', $part, 2); $part_value = trim($part_value); } $part_key = trim($part_key); $attributes[$part_key] = $part_value; } } return new static($name, $value, $attributes, [], $reference_time); } /** * Parse all Set-Cookie headers from request headers * * @param \WpOrg\Requests\Response\Headers $headers Headers to parse from * @param \WpOrg\Requests\Iri|null $origin URI for comparing cookie origins * @param int|null $time Reference time for expiration calculation * @return array * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $origin argument is not null or an instance of the Iri class. */ public static function parse_from_headers(Headers $headers, $origin = null, $time = null) { $cookie_headers = $headers->getValues('Set-Cookie'); if (empty($cookie_headers)) { return []; } if ($origin !== null && !($origin instanceof Iri)) { throw InvalidArgument::create(2, '$origin', Iri::class . ' or null', gettype($origin)); } $cookies = []; foreach ($cookie_headers as $header) { $parsed = self::parse($header, '', $time); // Default domain/path attributes if (empty($parsed->attributes['domain']) && !empty($origin)) { $parsed->attributes['domain'] = $origin->host; $parsed->flags['host-only'] = true; } else { $parsed->flags['host-only'] = false; } $path_is_valid = (!empty($parsed->attributes['path']) && $parsed->attributes['path'][0] === '/'); if (!$path_is_valid && !empty($origin)) { $path = $origin->path; // Default path normalization as per RFC 6265 section 5.1.4 if (substr($path, 0, 1) !== '/') { // If the uri-path is empty or if the first character of // the uri-path is not a %x2F ("/") character, output // %x2F ("/") and skip the remaining steps. $path = '/'; } elseif (substr_count($path, '/') === 1) { // If the uri-path contains no more than one %x2F ("/") // character, output %x2F ("/") and skip the remaining // step. $path = '/'; } else { // Output the characters of the uri-path from the first // character up to, but not including, the right-most // %x2F ("/"). $path = substr($path, 0, strrpos($path, '/')); } $parsed->attributes['path'] = $path; } // Reject invalid cookie domains if (!empty($origin) && !$parsed->domain_matches($origin->host)) { continue; } $cookies[$parsed->name] = $parsed; } return $cookies; } } PKDZ @9ффsrc/Requests.phpnuW+A 10, 'connect_timeout' => 10, 'useragent' => 'php-requests/' . self::VERSION, 'protocol_version' => 1.1, 'redirected' => 0, 'redirects' => 10, 'follow_redirects' => true, 'blocking' => true, 'type' => self::GET, 'filename' => false, 'auth' => false, 'proxy' => false, 'cookies' => false, 'max_bytes' => false, 'idn' => true, 'hooks' => null, 'transport' => null, 'verify' => null, 'verifyname' => true, ]; /** * Default supported Transport classes. * * @since 2.0.0 * * @var array */ const DEFAULT_TRANSPORTS = [ Curl::class => Curl::class, Fsockopen::class => Fsockopen::class, ]; /** * Current version of Requests * * @var string */ const VERSION = '2.0.11'; /** * Selected transport name * * Use {@see \WpOrg\Requests\Requests::get_transport()} instead * * @var array */ public static $transport = []; /** * Registered transport classes * * @var array */ protected static $transports = []; /** * Default certificate path. * * @see \WpOrg\Requests\Requests::get_certificate_path() * @see \WpOrg\Requests\Requests::set_certificate_path() * * @var string */ protected static $certificate_path = __DIR__ . '/../certificates/cacert.pem'; /** * All (known) valid deflate, gzip header magic markers. * * These markers relate to different compression levels. * * @link https://stackoverflow.com/a/43170354/482864 Marker source. * * @since 2.0.0 * * @var array */ private static $magic_compression_headers = [ "\x1f\x8b" => true, // Gzip marker. "\x78\x01" => true, // Zlib marker - level 1. "\x78\x5e" => true, // Zlib marker - level 2 to 5. "\x78\x9c" => true, // Zlib marker - level 6. "\x78\xda" => true, // Zlib marker - level 7 to 9. ]; /** * This is a static class, do not instantiate it * * @codeCoverageIgnore */ private function __construct() {} /** * Register a transport * * @param string $transport Transport class to add, must support the \WpOrg\Requests\Transport interface */ public static function add_transport($transport) { if (empty(self::$transports)) { self::$transports = self::DEFAULT_TRANSPORTS; } self::$transports[$transport] = $transport; } /** * Get the fully qualified class name (FQCN) for a working transport. * * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. * @return string FQCN of the transport to use, or an empty string if no transport was * found which provided the requested capabilities. */ protected static function get_transport_class(array $capabilities = []) { // Caching code, don't bother testing coverage. // @codeCoverageIgnoreStart // Array of capabilities as a string to be used as an array key. ksort($capabilities); $cap_string = serialize($capabilities); // Don't search for a transport if it's already been done for these $capabilities. if (isset(self::$transport[$cap_string])) { return self::$transport[$cap_string]; } // Ensure we will not run this same check again later on. self::$transport[$cap_string] = ''; // @codeCoverageIgnoreEnd if (empty(self::$transports)) { self::$transports = self::DEFAULT_TRANSPORTS; } // Find us a working transport. foreach (self::$transports as $class) { if (!class_exists($class)) { continue; } $result = $class::test($capabilities); if ($result === true) { self::$transport[$cap_string] = $class; break; } } return self::$transport[$cap_string]; } /** * Get a working transport. * * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. * @return \WpOrg\Requests\Transport * @throws \WpOrg\Requests\Exception If no valid transport is found (`notransport`). */ protected static function get_transport(array $capabilities = []) { $class = self::get_transport_class($capabilities); if ($class === '') { throw new Exception('No working transports found', 'notransport', self::$transports); } return new $class(); } /** * Checks to see if we have a transport for the capabilities requested. * * Supported capabilities can be found in the {@see \WpOrg\Requests\Capability} * interface as constants. * * Example usage: * `Requests::has_capabilities([Capability::SSL => true])`. * * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. * @return bool Whether the transport has the requested capabilities. */ public static function has_capabilities(array $capabilities = []) { return self::get_transport_class($capabilities) !== ''; } /**#@+ * @see \WpOrg\Requests\Requests::request() * @param string $url * @param array $headers * @param array $options * @return \WpOrg\Requests\Response */ /** * Send a GET request */ public static function get($url, $headers = [], $options = []) { return self::request($url, $headers, null, self::GET, $options); } /** * Send a HEAD request */ public static function head($url, $headers = [], $options = []) { return self::request($url, $headers, null, self::HEAD, $options); } /** * Send a DELETE request */ public static function delete($url, $headers = [], $options = []) { return self::request($url, $headers, null, self::DELETE, $options); } /** * Send a TRACE request */ public static function trace($url, $headers = [], $options = []) { return self::request($url, $headers, null, self::TRACE, $options); } /**#@-*/ /**#@+ * @see \WpOrg\Requests\Requests::request() * @param string $url * @param array $headers * @param array $data * @param array $options * @return \WpOrg\Requests\Response */ /** * Send a POST request */ public static function post($url, $headers = [], $data = [], $options = []) { return self::request($url, $headers, $data, self::POST, $options); } /** * Send a PUT request */ public static function put($url, $headers = [], $data = [], $options = []) { return self::request($url, $headers, $data, self::PUT, $options); } /** * Send an OPTIONS request */ public static function options($url, $headers = [], $data = [], $options = []) { return self::request($url, $headers, $data, self::OPTIONS, $options); } /** * Send a PATCH request * * Note: Unlike {@see \WpOrg\Requests\Requests::post()} and {@see \WpOrg\Requests\Requests::put()}, * `$headers` is required, as the specification recommends that should send an ETag * * @link https://tools.ietf.org/html/rfc5789 */ public static function patch($url, $headers, $data = [], $options = []) { return self::request($url, $headers, $data, self::PATCH, $options); } /**#@-*/ /** * Main interface for HTTP requests * * This method initiates a request and sends it via a transport before * parsing. * * The `$options` parameter takes an associative array with the following * options: * * - `timeout`: How long should we wait for a response? * Note: for cURL, a minimum of 1 second applies, as DNS resolution * operates at second-resolution only. * (float, seconds with a millisecond precision, default: 10, example: 0.01) * - `connect_timeout`: How long should we wait while trying to connect? * (float, seconds with a millisecond precision, default: 10, example: 0.01) * - `useragent`: Useragent to send to the server * (string, default: php-requests/$version) * - `follow_redirects`: Should we follow 3xx redirects? * (boolean, default: true) * - `redirects`: How many times should we redirect before erroring? * (integer, default: 10) * - `blocking`: Should we block processing on this request? * (boolean, default: true) * - `filename`: File to stream the body to instead. * (string|boolean, default: false) * - `auth`: Authentication handler or array of user/password details to use * for Basic authentication * (\WpOrg\Requests\Auth|array|boolean, default: false) * - `proxy`: Proxy details to use for proxy by-passing and authentication * (\WpOrg\Requests\Proxy|array|string|boolean, default: false) * - `max_bytes`: Limit for the response body size. * (integer|boolean, default: false) * - `idn`: Enable IDN parsing * (boolean, default: true) * - `transport`: Custom transport. Either a class name, or a * transport object. Defaults to the first working transport from * {@see \WpOrg\Requests\Requests::getTransport()} * (string|\WpOrg\Requests\Transport, default: {@see \WpOrg\Requests\Requests::getTransport()}) * - `hooks`: Hooks handler. * (\WpOrg\Requests\HookManager, default: new WpOrg\Requests\Hooks()) * - `verify`: Should we verify SSL certificates? Allows passing in a custom * certificate file as a string. (Using true uses the system-wide root * certificate store instead, but this may have different behaviour * across transports.) * (string|boolean, default: certificates/cacert.pem) * - `verifyname`: Should we verify the common name in the SSL certificate? * (boolean, default: true) * - `data_format`: How should we send the `$data` parameter? * (string, one of 'query' or 'body', default: 'query' for * HEAD/GET/DELETE, 'body' for POST/PUT/OPTIONS/PATCH) * * @param string|Stringable $url URL to request * @param array $headers Extra headers to send with the request * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests * @param string $type HTTP request type (use Requests constants) * @param array $options Options for the request (see description for more information) * @return \WpOrg\Requests\Response * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $type argument is not a string. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. * @throws \WpOrg\Requests\Exception On invalid URLs (`nonhttp`) */ public static function request($url, $headers = [], $data = [], $type = self::GET, $options = []) { if (InputValidator::is_string_or_stringable($url) === false) { throw InvalidArgument::create(1, '$url', 'string|Stringable', gettype($url)); } if (is_string($type) === false) { throw InvalidArgument::create(4, '$type', 'string', gettype($type)); } if (is_array($options) === false) { throw InvalidArgument::create(5, '$options', 'array', gettype($options)); } if (empty($options['type'])) { $options['type'] = $type; } $options = array_merge(self::get_default_options(), $options); self::set_defaults($url, $headers, $data, $type, $options); $options['hooks']->dispatch('requests.before_request', [&$url, &$headers, &$data, &$type, &$options]); if (!empty($options['transport'])) { $transport = $options['transport']; if (is_string($options['transport'])) { $transport = new $transport(); } } else { $need_ssl = (stripos($url, 'https://') === 0); $capabilities = [Capability::SSL => $need_ssl]; $transport = self::get_transport($capabilities); } $response = $transport->request($url, $headers, $data, $options); $options['hooks']->dispatch('requests.before_parse', [&$response, $url, $headers, $data, $type, $options]); return self::parse_response($response, $url, $headers, $data, $options); } /** * Send multiple HTTP requests simultaneously * * The `$requests` parameter takes an associative or indexed array of * request fields. The key of each request can be used to match up the * request with the returned data, or with the request passed into your * `multiple.request.complete` callback. * * The request fields value is an associative array with the following keys: * * - `url`: Request URL Same as the `$url` parameter to * {@see \WpOrg\Requests\Requests::request()} * (string, required) * - `headers`: Associative array of header fields. Same as the `$headers` * parameter to {@see \WpOrg\Requests\Requests::request()} * (array, default: `array()`) * - `data`: Associative array of data fields or a string. Same as the * `$data` parameter to {@see \WpOrg\Requests\Requests::request()} * (array|string, default: `array()`) * - `type`: HTTP request type (use \WpOrg\Requests\Requests constants). Same as the `$type` * parameter to {@see \WpOrg\Requests\Requests::request()} * (string, default: `\WpOrg\Requests\Requests::GET`) * - `cookies`: Associative array of cookie name to value, or cookie jar. * (array|\WpOrg\Requests\Cookie\Jar) * * If the `$options` parameter is specified, individual requests will * inherit options from it. This can be used to use a single hooking system, * or set all the types to `\WpOrg\Requests\Requests::POST`, for example. * * In addition, the `$options` parameter takes the following global options: * * - `complete`: A callback for when a request is complete. Takes two * parameters, a \WpOrg\Requests\Response/\WpOrg\Requests\Exception reference, and the * ID from the request array (Note: this can also be overridden on a * per-request basis, although that's a little silly) * (callback) * * @param array $requests Requests data (see description for more information) * @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()}) * @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object) * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. */ public static function request_multiple($requests, $options = []) { if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) { throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests)); } if (is_array($options) === false) { throw InvalidArgument::create(2, '$options', 'array', gettype($options)); } $options = array_merge(self::get_default_options(true), $options); if (!empty($options['hooks'])) { $options['hooks']->register('transport.internal.parse_response', [static::class, 'parse_multiple']); if (!empty($options['complete'])) { $options['hooks']->register('multiple.request.complete', $options['complete']); } } foreach ($requests as $id => &$request) { if (!isset($request['headers'])) { $request['headers'] = []; } if (!isset($request['data'])) { $request['data'] = []; } if (!isset($request['type'])) { $request['type'] = self::GET; } if (!isset($request['options'])) { $request['options'] = $options; $request['options']['type'] = $request['type']; } else { if (empty($request['options']['type'])) { $request['options']['type'] = $request['type']; } $request['options'] = array_merge($options, $request['options']); } self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']); // Ensure we only hook in once if ($request['options']['hooks'] !== $options['hooks']) { $request['options']['hooks']->register('transport.internal.parse_response', [static::class, 'parse_multiple']); if (!empty($request['options']['complete'])) { $request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']); } } } unset($request); if (!empty($options['transport'])) { $transport = $options['transport']; if (is_string($options['transport'])) { $transport = new $transport(); } } else { $transport = self::get_transport(); } $responses = $transport->request_multiple($requests, $options); foreach ($responses as $id => &$response) { // If our hook got messed with somehow, ensure we end up with the // correct response if (is_string($response)) { $request = $requests[$id]; self::parse_multiple($response, $request); $request['options']['hooks']->dispatch('multiple.request.complete', [&$response, $id]); } } return $responses; } /** * Get the default options * * @see \WpOrg\Requests\Requests::request() for values returned by this method * @param boolean $multirequest Is this a multirequest? * @return array Default option values */ protected static function get_default_options($multirequest = false) { $defaults = static::OPTION_DEFAULTS; $defaults['verify'] = self::$certificate_path; if ($multirequest !== false) { $defaults['complete'] = null; } return $defaults; } /** * Get default certificate path. * * @return string Default certificate path. */ public static function get_certificate_path() { return self::$certificate_path; } /** * Set default certificate path. * * @param string|Stringable|bool $path Certificate path, pointing to a PEM file. * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or boolean. */ public static function set_certificate_path($path) { if (InputValidator::is_string_or_stringable($path) === false && is_bool($path) === false) { throw InvalidArgument::create(1, '$path', 'string|Stringable|bool', gettype($path)); } self::$certificate_path = $path; } /** * Set the default values * * The $options parameter is updated with the results. * * @param string $url URL to request * @param array $headers Extra headers to send with the request * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests * @param string $type HTTP request type * @param array $options Options for the request * @return void * * @throws \WpOrg\Requests\Exception When the $url is not an http(s) URL. */ protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) { if (!preg_match('/^http(s)?:\/\//i', $url, $matches)) { throw new Exception('Only HTTP(S) requests are handled.', 'nonhttp', $url); } if (empty($options['hooks'])) { $options['hooks'] = new Hooks(); } if (is_array($options['auth'])) { $options['auth'] = new Basic($options['auth']); } if ($options['auth'] !== false) { $options['auth']->register($options['hooks']); } if (is_string($options['proxy']) || is_array($options['proxy'])) { $options['proxy'] = new Http($options['proxy']); } if ($options['proxy'] !== false) { $options['proxy']->register($options['hooks']); } if (is_array($options['cookies'])) { $options['cookies'] = new Jar($options['cookies']); } elseif (empty($options['cookies'])) { $options['cookies'] = new Jar(); } if ($options['cookies'] !== false) { $options['cookies']->register($options['hooks']); } if ($options['idn'] !== false) { $iri = new Iri($url); $iri->host = IdnaEncoder::encode($iri->ihost); $url = $iri->uri; } // Massage the type to ensure we support it. $type = strtoupper($type); if (!isset($options['data_format'])) { if (in_array($type, [self::HEAD, self::GET, self::DELETE], true)) { $options['data_format'] = 'query'; } else { $options['data_format'] = 'body'; } } } /** * HTTP response parser * * @param string $headers Full response text including headers and body * @param string $url Original request URL * @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects * @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects * @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects * @return \WpOrg\Requests\Response * * @throws \WpOrg\Requests\Exception On missing head/body separator (`requests.no_crlf_separator`) * @throws \WpOrg\Requests\Exception On missing head/body separator (`noversion`) * @throws \WpOrg\Requests\Exception On missing head/body separator (`toomanyredirects`) */ protected static function parse_response($headers, $url, $req_headers, $req_data, $options) { $return = new Response(); if (!$options['blocking']) { return $return; } $return->raw = $headers; $return->url = (string) $url; $return->body = ''; if (!$options['filename']) { $pos = strpos($headers, "\r\n\r\n"); if ($pos === false) { // Crap! throw new Exception('Missing header/body separator', 'requests.no_crlf_separator'); } $headers = substr($return->raw, 0, $pos); // Headers will always be separated from the body by two new lines - `\n\r\n\r`. $body = substr($return->raw, $pos + 4); if (!empty($body)) { $return->body = $body; } } // Pretend CRLF = LF for compatibility (RFC 2616, section 19.3) $headers = str_replace("\r\n", "\n", $headers); // Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2) $headers = preg_replace('/\n[ \t]/', ' ', $headers); $headers = explode("\n", $headers); preg_match('#^HTTP/(1\.\d)[ \t]+(\d+)#i', array_shift($headers), $matches); if (empty($matches)) { throw new Exception('Response could not be parsed', 'noversion', $headers); } $return->protocol_version = (float) $matches[1]; $return->status_code = (int) $matches[2]; if ($return->status_code >= 200 && $return->status_code < 300) { $return->success = true; } foreach ($headers as $header) { list($key, $value) = explode(':', $header, 2); $value = trim($value); preg_replace('#(\s+)#i', ' ', $value); $return->headers[$key] = $value; } if (isset($return->headers['transfer-encoding'])) { $return->body = self::decode_chunked($return->body); unset($return->headers['transfer-encoding']); } if (isset($return->headers['content-encoding'])) { $return->body = self::decompress($return->body); } //fsockopen and cURL compatibility if (isset($return->headers['connection'])) { unset($return->headers['connection']); } $options['hooks']->dispatch('requests.before_redirect_check', [&$return, $req_headers, $req_data, $options]); if ($return->is_redirect() && $options['follow_redirects'] === true) { if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) { if ($return->status_code === 303) { $options['type'] = self::GET; } $options['redirected']++; $location = $return->headers['location']; if (strpos($location, 'http://') !== 0 && strpos($location, 'https://') !== 0) { // relative redirect, for compatibility make it absolute $location = Iri::absolutize($url, $location); $location = $location->uri; } $hook_args = [ &$location, &$req_headers, &$req_data, &$options, $return, ]; $options['hooks']->dispatch('requests.before_redirect', $hook_args); $redirected = self::request($location, $req_headers, $req_data, $options['type'], $options); $redirected->history[] = $return; return $redirected; } elseif ($options['redirected'] >= $options['redirects']) { throw new Exception('Too many redirects', 'toomanyredirects', $return); } } $return->redirects = $options['redirected']; $options['hooks']->dispatch('requests.after_request', [&$return, $req_headers, $req_data, $options]); return $return; } /** * Callback for `transport.internal.parse_response` * * Internal use only. Converts a raw HTTP response to a \WpOrg\Requests\Response * while still executing a multiple request. * * `$response` is either set to a \WpOrg\Requests\Response instance, or a \WpOrg\Requests\Exception object * * @param string $response Full response text including headers and body (will be overwritten with Response instance) * @param array $request Request data as passed into {@see \WpOrg\Requests\Requests::request_multiple()} * @return void */ public static function parse_multiple(&$response, $request) { try { $url = $request['url']; $headers = $request['headers']; $data = $request['data']; $options = $request['options']; $response = self::parse_response($response, $url, $headers, $data, $options); } catch (Exception $e) { $response = $e; } } /** * Decoded a chunked body as per RFC 2616 * * @link https://tools.ietf.org/html/rfc2616#section-3.6.1 * @param string $data Chunked body * @return string Decoded body */ protected static function decode_chunked($data) { if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) { return $data; } $decoded = ''; $encoded = $data; while (true) { $is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches); if (!$is_chunked) { // Looks like it's not chunked after all return $data; } $length = hexdec(trim($matches[1])); if ($length === 0) { // Ignore trailer headers return $decoded; } $chunk_length = strlen($matches[0]); $decoded .= substr($encoded, $chunk_length, $length); $encoded = substr($encoded, $chunk_length + $length + 2); if (trim($encoded) === '0' || empty($encoded)) { return $decoded; } } // We'll never actually get down here // @codeCoverageIgnoreStart } // @codeCoverageIgnoreEnd /** * Convert a key => value array to a 'key: value' array for headers * * @param iterable $dictionary Dictionary of header values * @return array List of headers * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not iterable. */ public static function flatten($dictionary) { if (InputValidator::is_iterable($dictionary) === false) { throw InvalidArgument::create(1, '$dictionary', 'iterable', gettype($dictionary)); } $return = []; foreach ($dictionary as $key => $value) { $return[] = sprintf('%s: %s', $key, $value); } return $return; } /** * Decompress an encoded body * * Implements gzip, compress and deflate. Guesses which it is by attempting * to decode. * * @param string $data Compressed data in one of the above formats * @return string Decompressed string * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string. */ public static function decompress($data) { if (is_string($data) === false) { throw InvalidArgument::create(1, '$data', 'string', gettype($data)); } if (trim($data) === '') { // Empty body does not need further processing. return $data; } $marker = substr($data, 0, 2); if (!isset(self::$magic_compression_headers[$marker])) { // Not actually compressed. Probably cURL ruining this for us. return $data; } if (function_exists('gzdecode')) { $decoded = @gzdecode($data); if ($decoded !== false) { return $decoded; } } if (function_exists('gzinflate')) { $decoded = @gzinflate($data); if ($decoded !== false) { return $decoded; } } $decoded = self::compatible_gzinflate($data); if ($decoded !== false) { return $decoded; } if (function_exists('gzuncompress')) { $decoded = @gzuncompress($data); if ($decoded !== false) { return $decoded; } } return $data; } /** * Decompression of deflated string while staying compatible with the majority of servers. * * Certain Servers will return deflated data with headers which PHP's gzinflate() * function cannot handle out of the box. The following function has been created from * various snippets on the gzinflate() PHP documentation. * * Warning: Magic numbers within. Due to the potential different formats that the compressed * data may be returned in, some "magic offsets" are needed to ensure proper decompression * takes place. For a simple progmatic way to determine the magic offset in use, see: * https://core.trac.wordpress.org/ticket/18273 * * @since 1.6.0 * @link https://core.trac.wordpress.org/ticket/18273 * @link https://www.php.net/gzinflate#70875 * @link https://www.php.net/gzinflate#77336 * * @param string $gz_data String to decompress. * @return string|bool False on failure. * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string. */ public static function compatible_gzinflate($gz_data) { if (is_string($gz_data) === false) { throw InvalidArgument::create(1, '$gz_data', 'string', gettype($gz_data)); } if (trim($gz_data) === '') { return false; } // Compressed data might contain a full zlib header, if so strip it for // gzinflate() if (substr($gz_data, 0, 3) === "\x1f\x8b\x08") { $i = 10; $flg = ord(substr($gz_data, 3, 1)); if ($flg > 0) { if ($flg & 4) { list($xlen) = unpack('v', substr($gz_data, $i, 2)); $i += 2 + $xlen; } if ($flg & 8) { $i = strpos($gz_data, "\0", $i) + 1; } if ($flg & 16) { $i = strpos($gz_data, "\0", $i) + 1; } if ($flg & 2) { $i += 2; } } $decompressed = self::compatible_gzinflate(substr($gz_data, $i)); if ($decompressed !== false) { return $decompressed; } } // If the data is Huffman Encoded, we must first strip the leading 2 // byte Huffman marker for gzinflate() // The response is Huffman coded by many compressors such as // java.util.zip.Deflater, Ruby's Zlib::Deflate, and .NET's // System.IO.Compression.DeflateStream. // // See https://decompres.blogspot.com/ for a quick explanation of this // data type $huffman_encoded = false; // low nibble of first byte should be 0x08 list(, $first_nibble) = unpack('h', $gz_data); // First 2 bytes should be divisible by 0x1F list(, $first_two_bytes) = unpack('n', $gz_data); if ($first_nibble === 0x08 && ($first_two_bytes % 0x1F) === 0) { $huffman_encoded = true; } if ($huffman_encoded) { $decompressed = @gzinflate(substr($gz_data, 2)); if ($decompressed !== false) { return $decompressed; } } if (substr($gz_data, 0, 4) === "\x50\x4b\x03\x04") { // ZIP file format header // Offset 6: 2 bytes, General-purpose field // Offset 26: 2 bytes, filename length // Offset 28: 2 bytes, optional field length // Offset 30: Filename field, followed by optional field, followed // immediately by data list(, $general_purpose_flag) = unpack('v', substr($gz_data, 6, 2)); // If the file has been compressed on the fly, 0x08 bit is set of // the general purpose field. We can use this to differentiate // between a compressed document, and a ZIP file $zip_compressed_on_the_fly = ((0x08 & $general_purpose_flag) === 0x08); if (!$zip_compressed_on_the_fly) { // Don't attempt to decode a compressed zip file return $gz_data; } // Determine the first byte of data, based on the above ZIP header // offsets: $first_file_start = array_sum(unpack('v2', substr($gz_data, 26, 4))); $decompressed = @gzinflate(substr($gz_data, 30 + $first_file_start)); if ($decompressed !== false) { return $decompressed; } return false; } // Finally fall back to straight gzinflate $decompressed = @gzinflate($gz_data); if ($decompressed !== false) { return $decompressed; } // Fallback for all above failing, not expected, but included for // debugging and preventing regressions and to track stats $decompressed = @gzinflate(substr($gz_data, 2)); if ($decompressed !== false) { return $decompressed; } return false; } } PKDZ; src/Ipv6.phpnuW+A FF01:0:0:0:0:0:0:101 * ::1 -> 0:0:0:0:0:0:0:1 * * @author Alexander Merz * @author elfrink at introweb dot nl * @author Josh Peck * @copyright 2003-2005 The PHP Group * @license https://opensource.org/licenses/bsd-license.php * * @param string|Stringable $ip An IPv6 address * @return string The uncompressed IPv6 address * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object. */ public static function uncompress($ip) { if (InputValidator::is_string_or_stringable($ip) === false) { throw InvalidArgument::create(1, '$ip', 'string|Stringable', gettype($ip)); } $ip = (string) $ip; if (substr_count($ip, '::') !== 1) { return $ip; } list($ip1, $ip2) = explode('::', $ip); $c1 = ($ip1 === '') ? -1 : substr_count($ip1, ':'); $c2 = ($ip2 === '') ? -1 : substr_count($ip2, ':'); if (strpos($ip2, '.') !== false) { $c2++; } if ($c1 === -1 && $c2 === -1) { // :: $ip = '0:0:0:0:0:0:0:0'; } elseif ($c1 === -1) { // ::xxx $fill = str_repeat('0:', 7 - $c2); $ip = str_replace('::', $fill, $ip); } elseif ($c2 === -1) { // xxx:: $fill = str_repeat(':0', 7 - $c1); $ip = str_replace('::', $fill, $ip); } else { // xxx::xxx $fill = ':' . str_repeat('0:', 6 - $c2 - $c1); $ip = str_replace('::', $fill, $ip); } return $ip; } /** * Compresses an IPv6 address * * RFC 4291 allows you to compress consecutive zero pieces in an address to * '::'. This method expects a valid IPv6 address and compresses consecutive * zero pieces to '::'. * * Example: FF01:0:0:0:0:0:0:101 -> FF01::101 * 0:0:0:0:0:0:0:1 -> ::1 * * @see \WpOrg\Requests\Ipv6::uncompress() * * @param string $ip An IPv6 address * @return string The compressed IPv6 address */ public static function compress($ip) { // Prepare the IP to be compressed. // Note: Input validation is handled in the `uncompress()` method, which is the first call made in this method. $ip = self::uncompress($ip); $ip_parts = self::split_v6_v4($ip); // Replace all leading zeros $ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]); // Find bunches of zeros if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) { $max = 0; $pos = null; foreach ($matches[0] as $match) { if (strlen($match[0]) > $max) { $max = strlen($match[0]); $pos = $match[1]; } } $ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max); } if ($ip_parts[1] !== '') { return implode(':', $ip_parts); } else { return $ip_parts[0]; } } /** * Splits an IPv6 address into the IPv6 and IPv4 representation parts * * RFC 4291 allows you to represent the last two parts of an IPv6 address * using the standard IPv4 representation * * Example: 0:0:0:0:0:0:13.1.68.3 * 0:0:0:0:0:FFFF:129.144.52.38 * * @param string $ip An IPv6 address * @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part */ private static function split_v6_v4($ip) { if (strpos($ip, '.') !== false) { $pos = strrpos($ip, ':'); $ipv6_part = substr($ip, 0, $pos); $ipv4_part = substr($ip, $pos + 1); return [$ipv6_part, $ipv4_part]; } else { return [$ip, '']; } } /** * Checks an IPv6 address * * Checks if the given IP is a valid IPv6 address * * @param string $ip An IPv6 address * @return bool true if $ip is a valid IPv6 address */ public static function check_ipv6($ip) { // Note: Input validation is handled in the `uncompress()` method, which is the first call made in this method. $ip = self::uncompress($ip); list($ipv6, $ipv4) = self::split_v6_v4($ip); $ipv6 = explode(':', $ipv6); $ipv4 = explode('.', $ipv4); if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) { foreach ($ipv6 as $ipv6_part) { // The section can't be empty if ($ipv6_part === '') { return false; } // Nor can it be over four characters if (strlen($ipv6_part) > 4) { return false; } // Remove leading zeros (this is safe because of the above) $ipv6_part = ltrim($ipv6_part, '0'); if ($ipv6_part === '') { $ipv6_part = '0'; } // Check the value is valid $value = hexdec($ipv6_part); if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) { return false; } } if (count($ipv4) === 4) { foreach ($ipv4 as $ipv4_part) { $value = (int) $ipv4_part; if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) { return false; } } } return true; } else { return false; } } } PKDZy src/Hooks.phpnuW+A0 is executed later * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $hook argument is not a string. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $callback argument is not callable. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $priority argument is not an integer. */ public function register($hook, $callback, $priority = 0) { if (is_string($hook) === false) { throw InvalidArgument::create(1, '$hook', 'string', gettype($hook)); } if (is_callable($callback) === false) { throw InvalidArgument::create(2, '$callback', 'callable', gettype($callback)); } if (InputValidator::is_numeric_array_key($priority) === false) { throw InvalidArgument::create(3, '$priority', 'integer', gettype($priority)); } if (!isset($this->hooks[$hook])) { $this->hooks[$hook] = [ $priority => [], ]; } elseif (!isset($this->hooks[$hook][$priority])) { $this->hooks[$hook][$priority] = []; } $this->hooks[$hook][$priority][] = $callback; } /** * Dispatch a message * * @param string $hook Hook name * @param array $parameters Parameters to pass to callbacks * @return boolean Successfulness * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $hook argument is not a string. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $parameters argument is not an array. */ public function dispatch($hook, $parameters = []) { if (is_string($hook) === false) { throw InvalidArgument::create(1, '$hook', 'string', gettype($hook)); } // Check strictly against array, as Array* objects don't work in combination with `call_user_func_array()`. if (is_array($parameters) === false) { throw InvalidArgument::create(2, '$parameters', 'array', gettype($parameters)); } if (empty($this->hooks[$hook])) { return false; } if (!empty($parameters)) { // Strip potential keys from the array to prevent them being interpreted as parameter names in PHP 8.0. $parameters = array_values($parameters); } ksort($this->hooks[$hook]); foreach ($this->hooks[$hook] as $priority => $hooked) { foreach ($hooked as $callback) { $callback(...$parameters); } } return true; } public function __wakeup() { throw new \LogicException( __CLASS__ . ' should never be unserialized' ); } } PKDZחsrc/Response.phpnuW+Aheaders = new Headers(); $this->cookies = new Jar(); } /** * Is the response a redirect? * * @return boolean True if redirect (3xx status), false if not. */ public function is_redirect() { $code = $this->status_code; return in_array($code, [300, 301, 302, 303, 307], true) || $code > 307 && $code < 400; } /** * Throws an exception if the request was not successful * * @param boolean $allow_redirects Set to false to throw on a 3xx as well * * @throws \WpOrg\Requests\Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`) * @throws \WpOrg\Requests\Exception\Http On non-successful status code. Exception class corresponds to "Status" + code (e.g. {@see \WpOrg\Requests\Exception\Http\Status404}) */ public function throw_for_status($allow_redirects = true) { if ($this->is_redirect()) { if ($allow_redirects !== true) { throw new Exception('Redirection not allowed', 'response.no_redirects', $this); } } elseif (!$this->success) { $exception = Http::get_class($this->status_code); throw new $exception(null, $this); } } /** * JSON decode the response body. * * The method parameters are the same as those for the PHP native `json_decode()` function. * * @link https://php.net/json-decode * * @param bool|null $associative Optional. When `true`, JSON objects will be returned as associative arrays; * When `false`, JSON objects will be returned as objects. * When `null`, JSON objects will be returned as associative arrays * or objects depending on whether `JSON_OBJECT_AS_ARRAY` is set in the flags. * Defaults to `true` (in contrast to the PHP native default of `null`). * @param int $depth Optional. Maximum nesting depth of the structure being decoded. * Defaults to `512`. * @param int $options Optional. Bitmask of JSON_BIGINT_AS_STRING, JSON_INVALID_UTF8_IGNORE, * JSON_INVALID_UTF8_SUBSTITUTE, JSON_OBJECT_AS_ARRAY, JSON_THROW_ON_ERROR. * Defaults to `0` (no options set). * * @return array * * @throws \WpOrg\Requests\Exception If `$this->body` is not valid json. */ public function decode_body($associative = true, $depth = 512, $options = 0) { $data = json_decode($this->body, $associative, $depth, $options); if (json_last_error() !== JSON_ERROR_NONE) { $last_error = json_last_error_msg(); throw new Exception('Unable to parse JSON data: ' . $last_error, 'response.invalid', $this); } return $data; } } PKDZrw$w$src/Autoload.phpnuW+A '\WpOrg\Requests\Auth', 'requests_hooker' => '\WpOrg\Requests\HookManager', 'requests_proxy' => '\WpOrg\Requests\Proxy', 'requests_transport' => '\WpOrg\Requests\Transport', // Classes. 'requests_cookie' => '\WpOrg\Requests\Cookie', 'requests_exception' => '\WpOrg\Requests\Exception', 'requests_hooks' => '\WpOrg\Requests\Hooks', 'requests_idnaencoder' => '\WpOrg\Requests\IdnaEncoder', 'requests_ipv6' => '\WpOrg\Requests\Ipv6', 'requests_iri' => '\WpOrg\Requests\Iri', 'requests_response' => '\WpOrg\Requests\Response', 'requests_session' => '\WpOrg\Requests\Session', 'requests_ssl' => '\WpOrg\Requests\Ssl', 'requests_auth_basic' => '\WpOrg\Requests\Auth\Basic', 'requests_cookie_jar' => '\WpOrg\Requests\Cookie\Jar', 'requests_proxy_http' => '\WpOrg\Requests\Proxy\Http', 'requests_response_headers' => '\WpOrg\Requests\Response\Headers', 'requests_transport_curl' => '\WpOrg\Requests\Transport\Curl', 'requests_transport_fsockopen' => '\WpOrg\Requests\Transport\Fsockopen', 'requests_utility_caseinsensitivedictionary' => '\WpOrg\Requests\Utility\CaseInsensitiveDictionary', 'requests_utility_filterediterator' => '\WpOrg\Requests\Utility\FilteredIterator', 'requests_exception_http' => '\WpOrg\Requests\Exception\Http', 'requests_exception_transport' => '\WpOrg\Requests\Exception\Transport', 'requests_exception_transport_curl' => '\WpOrg\Requests\Exception\Transport\Curl', 'requests_exception_http_304' => '\WpOrg\Requests\Exception\Http\Status304', 'requests_exception_http_305' => '\WpOrg\Requests\Exception\Http\Status305', 'requests_exception_http_306' => '\WpOrg\Requests\Exception\Http\Status306', 'requests_exception_http_400' => '\WpOrg\Requests\Exception\Http\Status400', 'requests_exception_http_401' => '\WpOrg\Requests\Exception\Http\Status401', 'requests_exception_http_402' => '\WpOrg\Requests\Exception\Http\Status402', 'requests_exception_http_403' => '\WpOrg\Requests\Exception\Http\Status403', 'requests_exception_http_404' => '\WpOrg\Requests\Exception\Http\Status404', 'requests_exception_http_405' => '\WpOrg\Requests\Exception\Http\Status405', 'requests_exception_http_406' => '\WpOrg\Requests\Exception\Http\Status406', 'requests_exception_http_407' => '\WpOrg\Requests\Exception\Http\Status407', 'requests_exception_http_408' => '\WpOrg\Requests\Exception\Http\Status408', 'requests_exception_http_409' => '\WpOrg\Requests\Exception\Http\Status409', 'requests_exception_http_410' => '\WpOrg\Requests\Exception\Http\Status410', 'requests_exception_http_411' => '\WpOrg\Requests\Exception\Http\Status411', 'requests_exception_http_412' => '\WpOrg\Requests\Exception\Http\Status412', 'requests_exception_http_413' => '\WpOrg\Requests\Exception\Http\Status413', 'requests_exception_http_414' => '\WpOrg\Requests\Exception\Http\Status414', 'requests_exception_http_415' => '\WpOrg\Requests\Exception\Http\Status415', 'requests_exception_http_416' => '\WpOrg\Requests\Exception\Http\Status416', 'requests_exception_http_417' => '\WpOrg\Requests\Exception\Http\Status417', 'requests_exception_http_418' => '\WpOrg\Requests\Exception\Http\Status418', 'requests_exception_http_428' => '\WpOrg\Requests\Exception\Http\Status428', 'requests_exception_http_429' => '\WpOrg\Requests\Exception\Http\Status429', 'requests_exception_http_431' => '\WpOrg\Requests\Exception\Http\Status431', 'requests_exception_http_500' => '\WpOrg\Requests\Exception\Http\Status500', 'requests_exception_http_501' => '\WpOrg\Requests\Exception\Http\Status501', 'requests_exception_http_502' => '\WpOrg\Requests\Exception\Http\Status502', 'requests_exception_http_503' => '\WpOrg\Requests\Exception\Http\Status503', 'requests_exception_http_504' => '\WpOrg\Requests\Exception\Http\Status504', 'requests_exception_http_505' => '\WpOrg\Requests\Exception\Http\Status505', 'requests_exception_http_511' => '\WpOrg\Requests\Exception\Http\Status511', 'requests_exception_http_unknown' => '\WpOrg\Requests\Exception\Http\StatusUnknown', ]; /** * Register the autoloader. * * Note: the autoloader is *prepended* in the autoload queue. * This is done to ensure that the Requests 2.0 autoloader takes precedence * over a potentially (dependency-registered) Requests 1.x autoloader. * * @internal This method contains a safeguard against the autoloader being * registered multiple times. This safeguard uses a global constant to * (hopefully/in most cases) still function correctly, even if the * class would be renamed. * * @return void */ public static function register() { if (defined('REQUESTS_AUTOLOAD_REGISTERED') === false) { spl_autoload_register([self::class, 'load'], true); define('REQUESTS_AUTOLOAD_REGISTERED', true); } } /** * Autoloader. * * @param string $class_name Name of the class name to load. * * @return bool Whether a class was loaded or not. */ public static function load($class_name) { // Check that the class starts with "Requests" (PSR-0) or "WpOrg\Requests" (PSR-4). $psr_4_prefix_pos = strpos($class_name, 'WpOrg\\Requests\\'); if (stripos($class_name, 'Requests') !== 0 && $psr_4_prefix_pos !== 0) { return false; } $class_lower = strtolower($class_name); if ($class_lower === 'requests') { // Reference to the original PSR-0 Requests class. $file = dirname(__DIR__) . '/library/Requests.php'; } elseif ($psr_4_prefix_pos === 0) { // PSR-4 classname. $file = __DIR__ . '/' . strtr(substr($class_name, 15), '\\', '/') . '.php'; } if (isset($file) && file_exists($file)) { include $file; return true; } /* * Okay, so the class starts with "Requests", but we couldn't find the file. * If this is one of the deprecated/renamed PSR-0 classes being requested, * let's alias it to the new name and throw a deprecation notice. */ if (isset(self::$deprecated_classes[$class_lower])) { /* * Integrators who cannot yet upgrade to the PSR-4 class names can silence deprecations * by defining a `REQUESTS_SILENCE_PSR0_DEPRECATIONS` constant and setting it to `true`. * The constant needs to be defined before the first deprecated class is requested * via this autoloader. */ if (!defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS') || REQUESTS_SILENCE_PSR0_DEPRECATIONS !== true) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error trigger_error( 'The PSR-0 `Requests_...` class names in the Requests library are deprecated.' . ' Switch to the PSR-4 `WpOrg\Requests\...` class names at your earliest convenience.', E_USER_DEPRECATED ); // Prevent the deprecation notice from being thrown twice. if (!defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS')) { define('REQUESTS_SILENCE_PSR0_DEPRECATIONS', true); } } // Create an alias and let the autoloader recursively kick in to load the PSR-4 class. return class_alias(self::$deprecated_classes[$class_lower], $class_name, true); } return false; } } } PKDZՄecc src/Proxy.phpnuW+A $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. * @return bool Whether the transport can be used. */ public static function test($capabilities = []); } PKDZ_ZZsrc/Exception.phpnuW+Atype = $type; $this->data = $data; } /** * Like {@see \Exception::getCode()}, but a string code. * * @codeCoverageIgnore * @return string */ public function getType() { return $this->type; } /** * Gives any relevant data * * @codeCoverageIgnore * @return mixed */ public function getData() { return $this->data; } } PKDZ84  src/Cookie/Jar.phpnuW+Acookies = $cookies; } /** * Normalise cookie data into a \WpOrg\Requests\Cookie * * @param string|\WpOrg\Requests\Cookie $cookie Cookie header value, possibly pre-parsed (object). * @param string $key Optional. The name for this cookie. * @return \WpOrg\Requests\Cookie */ public function normalize_cookie($cookie, $key = '') { if ($cookie instanceof Cookie) { return $cookie; } return Cookie::parse($cookie, $key); } /** * Check if the given item exists * * @param string $offset Item key * @return boolean Does the item exist? */ #[ReturnTypeWillChange] public function offsetExists($offset) { return isset($this->cookies[$offset]); } /** * Get the value for the item * * @param string $offset Item key * @return string|null Item value (null if offsetExists is false) */ #[ReturnTypeWillChange] public function offsetGet($offset) { if (!isset($this->cookies[$offset])) { return null; } return $this->cookies[$offset]; } /** * Set the given item * * @param string $offset Item name * @param string $value Item value * * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`) */ #[ReturnTypeWillChange] public function offsetSet($offset, $value) { if ($offset === null) { throw new Exception('Object is a dictionary, not a list', 'invalidset'); } $this->cookies[$offset] = $value; } /** * Unset the given header * * @param string $offset The key for the item to unset. */ #[ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->cookies[$offset]); } /** * Get an iterator for the data * * @return \ArrayIterator */ #[ReturnTypeWillChange] public function getIterator() { return new ArrayIterator($this->cookies); } /** * Register the cookie handler with the request's hooking system * * @param \WpOrg\Requests\HookManager $hooks Hooking system */ public function register(HookManager $hooks) { $hooks->register('requests.before_request', [$this, 'before_request']); $hooks->register('requests.before_redirect_check', [$this, 'before_redirect_check']); } /** * Add Cookie header to a request if we have any * * As per RFC 6265, cookies are separated by '; ' * * @param string $url * @param array $headers * @param array $data * @param string $type * @param array $options */ public function before_request($url, &$headers, &$data, &$type, &$options) { if (!$url instanceof Iri) { $url = new Iri($url); } if (!empty($this->cookies)) { $cookies = []; foreach ($this->cookies as $key => $cookie) { $cookie = $this->normalize_cookie($cookie, $key); // Skip expired cookies if ($cookie->is_expired()) { continue; } if ($cookie->domain_matches($url->host)) { $cookies[] = $cookie->format_for_header(); } } $headers['Cookie'] = implode('; ', $cookies); } } /** * Parse all cookies from a response and attach them to the response * * @param \WpOrg\Requests\Response $response Response as received. */ public function before_redirect_check(Response $response) { $url = $response->url; if (!$url instanceof Iri) { $url = new Iri($url); } $cookies = Cookie::parse_from_headers($response->headers, $url); $this->cookies = array_merge($this->cookies, $cookies); $response->cookies = $this; } } PKDZ/yQ##src/Session.phpnuW+Auseragent = 'X';` * * @var array */ public $options = []; /** * Create a new session * * @param string|Stringable|null $url Base URL for requests * @param array $headers Default headers for requests * @param array $data Default data for requests * @param array $options Default options for requests * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or null. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data argument is not an array. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. */ public function __construct($url = null, $headers = [], $data = [], $options = []) { if ($url !== null && InputValidator::is_string_or_stringable($url) === false) { throw InvalidArgument::create(1, '$url', 'string|Stringable|null', gettype($url)); } if (is_array($headers) === false) { throw InvalidArgument::create(2, '$headers', 'array', gettype($headers)); } if (is_array($data) === false) { throw InvalidArgument::create(3, '$data', 'array', gettype($data)); } if (is_array($options) === false) { throw InvalidArgument::create(4, '$options', 'array', gettype($options)); } $this->url = $url; $this->headers = $headers; $this->data = $data; $this->options = $options; if (empty($this->options['cookies'])) { $this->options['cookies'] = new Jar(); } } /** * Get a property's value * * @param string $name Property name. * @return mixed|null Property value, null if none found */ public function __get($name) { if (isset($this->options[$name])) { return $this->options[$name]; } return null; } /** * Set a property's value * * @param string $name Property name. * @param mixed $value Property value */ public function __set($name, $value) { $this->options[$name] = $value; } /** * Remove a property's value * * @param string $name Property name. */ public function __isset($name) { return isset($this->options[$name]); } /** * Remove a property's value * * @param string $name Property name. */ public function __unset($name) { unset($this->options[$name]); } /**#@+ * @see \WpOrg\Requests\Session::request() * @param string $url * @param array $headers * @param array $options * @return \WpOrg\Requests\Response */ /** * Send a GET request */ public function get($url, $headers = [], $options = []) { return $this->request($url, $headers, null, Requests::GET, $options); } /** * Send a HEAD request */ public function head($url, $headers = [], $options = []) { return $this->request($url, $headers, null, Requests::HEAD, $options); } /** * Send a DELETE request */ public function delete($url, $headers = [], $options = []) { return $this->request($url, $headers, null, Requests::DELETE, $options); } /**#@-*/ /**#@+ * @see \WpOrg\Requests\Session::request() * @param string $url * @param array $headers * @param array $data * @param array $options * @return \WpOrg\Requests\Response */ /** * Send a POST request */ public function post($url, $headers = [], $data = [], $options = []) { return $this->request($url, $headers, $data, Requests::POST, $options); } /** * Send a PUT request */ public function put($url, $headers = [], $data = [], $options = []) { return $this->request($url, $headers, $data, Requests::PUT, $options); } /** * Send a PATCH request * * Note: Unlike {@see \WpOrg\Requests\Session::post()} and {@see \WpOrg\Requests\Session::put()}, * `$headers` is required, as the specification recommends that should send an ETag * * @link https://tools.ietf.org/html/rfc5789 */ public function patch($url, $headers, $data = [], $options = []) { return $this->request($url, $headers, $data, Requests::PATCH, $options); } /**#@-*/ /** * Main interface for HTTP requests * * This method initiates a request and sends it via a transport before * parsing. * * @see \WpOrg\Requests\Requests::request() * * @param string $url URL to request * @param array $headers Extra headers to send with the request * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests * @param string $type HTTP request type (use \WpOrg\Requests\Requests constants) * @param array $options Options for the request (see {@see \WpOrg\Requests\Requests::request()}) * @return \WpOrg\Requests\Response * * @throws \WpOrg\Requests\Exception On invalid URLs (`nonhttp`) */ public function request($url, $headers = [], $data = [], $type = Requests::GET, $options = []) { $request = $this->merge_request(compact('url', 'headers', 'data', 'options')); return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']); } /** * Send multiple HTTP requests simultaneously * * @see \WpOrg\Requests\Requests::request_multiple() * * @param array $requests Requests data (see {@see \WpOrg\Requests\Requests::request_multiple()}) * @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()}) * @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object) * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. */ public function request_multiple($requests, $options = []) { if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) { throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests)); } if (is_array($options) === false) { throw InvalidArgument::create(2, '$options', 'array', gettype($options)); } foreach ($requests as $key => $request) { $requests[$key] = $this->merge_request($request, false); } $options = array_merge($this->options, $options); // Disallow forcing the type, as that's a per request setting unset($options['type']); return Requests::request_multiple($requests, $options); } public function __wakeup() { throw new \LogicException( __CLASS__ . ' should never be unserialized' ); } /** * Merge a request's data with the default data * * @param array $request Request data (same form as {@see \WpOrg\Requests\Session::request_multiple()}) * @param boolean $merge_options Should we merge options as well? * @return array Request data */ protected function merge_request($request, $merge_options = true) { if ($this->url !== null) { $request['url'] = Iri::absolutize($this->url, $request['url']); $request['url'] = $request['url']->uri; } if (empty($request['headers'])) { $request['headers'] = []; } $request['headers'] = array_merge($this->headers, $request['headers']); if (empty($request['data'])) { if (is_array($this->data)) { $request['data'] = $this->data; } } elseif (is_array($request['data']) && is_array($this->data)) { $request['data'] = array_merge($this->data, $request['data']); } if ($merge_options === true) { $request['options'] = array_merge($this->options, $request['options']); // Disallow forcing the type, as that's a per request setting unset($request['options']['type']); } return $request; } } PKDZ5 L )src/Utility/CaseInsensitiveDictionary.phpnuW+A $value) { $this->offsetSet($offset, $value); } } /** * Check if the given item exists * * @param string $offset Item key * @return boolean Does the item exist? */ #[ReturnTypeWillChange] public function offsetExists($offset) { if (is_string($offset)) { $offset = strtolower($offset); } return isset($this->data[$offset]); } /** * Get the value for the item * * @param string $offset Item key * @return string|null Item value (null if the item key doesn't exist) */ #[ReturnTypeWillChange] public function offsetGet($offset) { if (is_string($offset)) { $offset = strtolower($offset); } if (!isset($this->data[$offset])) { return null; } return $this->data[$offset]; } /** * Set the given item * * @param string $offset Item name * @param string $value Item value * * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`) */ #[ReturnTypeWillChange] public function offsetSet($offset, $value) { if ($offset === null) { throw new Exception('Object is a dictionary, not a list', 'invalidset'); } if (is_string($offset)) { $offset = strtolower($offset); } $this->data[$offset] = $value; } /** * Unset the given header * * @param string $offset The key for the item to unset. */ #[ReturnTypeWillChange] public function offsetUnset($offset) { if (is_string($offset)) { $offset = strtolower($offset); } unset($this->data[$offset]); } /** * Get an iterator for the data * * @return \ArrayIterator */ #[ReturnTypeWillChange] public function getIterator() { return new ArrayIterator($this->data); } /** * Get the headers as an array * * @return array Header data */ public function getAll() { return $this->data; } } PKDZmm src/Utility/FilteredIterator.phpnuW+Acallback = $callback; } } /** * Prevent unserialization of the object for security reasons. * * @phpcs:disable PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__unserializeFound * * @param array $data Restored array of data originally serialized. * * @return void */ #[ReturnTypeWillChange] public function __unserialize($data) {} // phpcs:enable /** * Perform reinitialization tasks. * * Prevents a callback from being injected during unserialization of an object. * * @return void */ public function __wakeup() { unset($this->callback); } /** * Get the current item's value after filtering * * @return string */ #[ReturnTypeWillChange] public function current() { $value = parent::current(); if (is_callable($this->callback)) { $value = call_user_func($this->callback, $value); } return $value; } /** * Prevent creating a PHP value from a stored representation of the object for security reasons. * * @param string $data The serialized string. * * @return void */ #[ReturnTypeWillChange] public function unserialize($data) {} } PKDZ^ src/Utility/InputValidator.phpnuW+A 0) { if ($position + $length > $strlen) { throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); } for ($position++; $remaining > 0; $position++) { $value = ord($input[$position]); // If it is invalid, count the sequence as invalid and reprocess the current byte: if (($value & 0xC0) !== 0x80) { throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); } --$remaining; $character |= ($value & 0x3F) << ($remaining * 6); } $position--; } if (// Non-shortest form sequences are invalid $length > 1 && $character <= 0x7F || $length > 2 && $character <= 0x7FF || $length > 3 && $character <= 0xFFFF // Outside of range of ucschar codepoints // Noncharacters || ($character & 0xFFFE) === 0xFFFE || $character >= 0xFDD0 && $character <= 0xFDEF || ( // Everything else not in ucschar $character > 0xD7FF && $character < 0xF900 || $character < 0x20 || $character > 0x7E && $character < 0xA0 || $character > 0xEFFFD ) ) { throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character); } $codepoints[] = $character; } return $codepoints; } /** * RFC3492-compliant encoder * * @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code * * @param string $input UTF-8 encoded string to encode * @return string Punycode-encoded string * * @throws \WpOrg\Requests\Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`) */ public static function punycode_encode($input) { $output = ''; // let n = initial_n $n = self::BOOTSTRAP_INITIAL_N; // let delta = 0 $delta = 0; // let bias = initial_bias $bias = self::BOOTSTRAP_INITIAL_BIAS; // let h = b = the number of basic code points in the input $h = 0; $b = 0; // see loop // copy them to the output in order $codepoints = self::utf8_to_codepoints($input); $extended = []; foreach ($codepoints as $char) { if ($char < 128) { // Character is valid ASCII // TODO: this should also check if it's valid for a URL $output .= chr($char); $h++; // Check if the character is non-ASCII, but below initial n // This never occurs for Punycode, so ignore in coverage // @codeCoverageIgnoreStart } elseif ($char < $n) { throw new Exception('Invalid character', 'idna.character_outside_domain', $char); // @codeCoverageIgnoreEnd } else { $extended[$char] = true; } } $extended = array_keys($extended); sort($extended); $b = $h; // [copy them] followed by a delimiter if b > 0 if (strlen($output) > 0) { $output .= '-'; } // {if the input contains a non-basic code point < n then fail} // while h < length(input) do begin $codepointcount = count($codepoints); while ($h < $codepointcount) { // let m = the minimum code point >= n in the input $m = array_shift($extended); //printf('next code point to insert is %s' . PHP_EOL, dechex($m)); // let delta = delta + (m - n) * (h + 1), fail on overflow $delta += ($m - $n) * ($h + 1); // let n = m $n = $m; // for each code point c in the input (in order) do begin for ($num = 0; $num < $codepointcount; $num++) { $c = $codepoints[$num]; // if c < n then increment delta, fail on overflow if ($c < $n) { $delta++; } elseif ($c === $n) { // if c == n then begin // let q = delta $q = $delta; // for k = base to infinity in steps of base do begin for ($k = self::BOOTSTRAP_BASE; ; $k += self::BOOTSTRAP_BASE) { // let t = tmin if k <= bias {+ tmin}, or // tmax if k >= bias + tmax, or k - bias otherwise if ($k <= ($bias + self::BOOTSTRAP_TMIN)) { $t = self::BOOTSTRAP_TMIN; } elseif ($k >= ($bias + self::BOOTSTRAP_TMAX)) { $t = self::BOOTSTRAP_TMAX; } else { $t = $k - $bias; } // if q < t then break if ($q < $t) { break; } // output the code point for digit t + ((q - t) mod (base - t)) $digit = (int) ($t + (($q - $t) % (self::BOOTSTRAP_BASE - $t))); $output .= self::digit_to_char($digit); // let q = (q - t) div (base - t) $q = (int) floor(($q - $t) / (self::BOOTSTRAP_BASE - $t)); } // end // output the code point for digit q $output .= self::digit_to_char($q); // let bias = adapt(delta, h + 1, test h equals b?) $bias = self::adapt($delta, $h + 1, $h === $b); // let delta = 0 $delta = 0; // increment h $h++; } // end } // end // increment delta and n $delta++; $n++; } // end return $output; } /** * Convert a digit to its respective character * * @link https://tools.ietf.org/html/rfc3492#section-5 * * @param int $digit Digit in the range 0-35 * @return string Single character corresponding to digit * * @throws \WpOrg\Requests\Exception On invalid digit (`idna.invalid_digit`) */ protected static function digit_to_char($digit) { // @codeCoverageIgnoreStart // As far as I know, this never happens, but still good to be sure. if ($digit < 0 || $digit > 35) { throw new Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit); } // @codeCoverageIgnoreEnd $digits = 'abcdefghijklmnopqrstuvwxyz0123456789'; return substr($digits, $digit, 1); } /** * Adapt the bias * * @link https://tools.ietf.org/html/rfc3492#section-6.1 * @param int $delta * @param int $numpoints * @param bool $firsttime * @return int|float New bias * * function adapt(delta,numpoints,firsttime): */ protected static function adapt($delta, $numpoints, $firsttime) { // if firsttime then let delta = delta div damp if ($firsttime) { $delta = floor($delta / self::BOOTSTRAP_DAMP); } else { // else let delta = delta div 2 $delta = floor($delta / 2); } // let delta = delta + (delta div numpoints) $delta += floor($delta / $numpoints); // let k = 0 $k = 0; // while delta > ((base - tmin) * tmax) div 2 do begin $max = floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX) / 2); while ($delta > $max) { // let delta = delta div (base - tmin) $delta = floor($delta / (self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN)); // let k = k + base $k += self::BOOTSTRAP_BASE; } // end // return k + (((base - tmin + 1) * delta) div (delta + skew)) return $k + floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN + 1) * $delta) / ($delta + self::BOOTSTRAP_SKEW)); } } PKDZg^yysrc/Proxy/Http.phpnuW+Aproxy = $args; } elseif (is_array($args)) { if (count($args) === 1) { list($this->proxy) = $args; } elseif (count($args) === 3) { list($this->proxy, $this->user, $this->pass) = $args; $this->use_authentication = true; } else { throw ArgumentCount::create( 'an array with exactly one element or exactly three elements', count($args), 'proxyhttpbadargs' ); } } elseif ($args !== null) { throw InvalidArgument::create(1, '$args', 'array|string|null', gettype($args)); } } /** * Register the necessary callbacks * * @since 1.6 * @see \WpOrg\Requests\Proxy\Http::curl_before_send() * @see \WpOrg\Requests\Proxy\Http::fsockopen_remote_socket() * @see \WpOrg\Requests\Proxy\Http::fsockopen_remote_host_path() * @see \WpOrg\Requests\Proxy\Http::fsockopen_header() * @param \WpOrg\Requests\Hooks $hooks Hook system */ public function register(Hooks $hooks) { $hooks->register('curl.before_send', [$this, 'curl_before_send']); $hooks->register('fsockopen.remote_socket', [$this, 'fsockopen_remote_socket']); $hooks->register('fsockopen.remote_host_path', [$this, 'fsockopen_remote_host_path']); if ($this->use_authentication) { $hooks->register('fsockopen.after_headers', [$this, 'fsockopen_header']); } } /** * Set cURL parameters before the data is sent * * @since 1.6 * @param resource|\CurlHandle $handle cURL handle */ public function curl_before_send(&$handle) { curl_setopt($handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); curl_setopt($handle, CURLOPT_PROXY, $this->proxy); if ($this->use_authentication) { curl_setopt($handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY); curl_setopt($handle, CURLOPT_PROXYUSERPWD, $this->get_auth_string()); } } /** * Alter remote socket information before opening socket connection * * @since 1.6 * @param string $remote_socket Socket connection string */ public function fsockopen_remote_socket(&$remote_socket) { $remote_socket = $this->proxy; } /** * Alter remote path before getting stream data * * @since 1.6 * @param string $path Path to send in HTTP request string ("GET ...") * @param string $url Full URL we're requesting */ public function fsockopen_remote_host_path(&$path, $url) { $path = $url; } /** * Add extra headers to the request before sending * * @since 1.6 * @param string $out HTTP header string */ public function fsockopen_header(&$out) { $out .= sprintf("Proxy-Authorization: Basic %s\r\n", base64_encode($this->get_auth_string())); } /** * Get the authentication string (user:pass) * * @since 1.6 * @return string */ public function get_auth_string() { return $this->user . ':' . $this->pass; } } PKDZ^  src/Response/Headers.phpnuW+Adata[$offset])) { return null; } return $this->flatten($this->data[$offset]); } /** * Set the given item * * @param string $offset Item name * @param string $value Item value * * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`) */ public function offsetSet($offset, $value) { if ($offset === null) { throw new Exception('Object is a dictionary, not a list', 'invalidset'); } if (is_string($offset)) { $offset = strtolower($offset); } if (!isset($this->data[$offset])) { $this->data[$offset] = []; } $this->data[$offset][] = $value; } /** * Get all values for a given header * * @param string $offset Name of the header to retrieve. * @return array|null Header values * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not valid as an array key. */ public function getValues($offset) { if (!is_string($offset) && !is_int($offset)) { throw InvalidArgument::create(1, '$offset', 'string|int', gettype($offset)); } if (is_string($offset)) { $offset = strtolower($offset); } if (!isset($this->data[$offset])) { return null; } return $this->data[$offset]; } /** * Flattens a value into a string * * Converts an array into a string by imploding values with a comma, as per * RFC2616's rules for folding headers. * * @param string|array $value Value to flatten * @return string Flattened value * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or an array. */ public function flatten($value) { if (is_string($value)) { return $value; } if (is_array($value)) { return implode(',', $value); } throw InvalidArgument::create(1, '$value', 'string|array', gettype($value)); } /** * Get an iterator for the data * * Converts the internally stored values to a comma-separated string if there is more * than one value for a key. * * @return \ArrayIterator */ public function getIterator() { return new FilteredIterator($this->data, [$this, 'flatten']); } } PKDZޣ@src/Capability.phpnuW+A 0) { // Whitespace detected. This can never be a dNSName. return false; } $parts = explode('.', $reference); if ($parts !== array_filter($parts)) { // DNSName cannot contain two dots next to each other. return false; } // Check the first part of the name $first = array_shift($parts); if (strpos($first, '*') !== false) { // Check that the wildcard is the full part if ($first !== '*') { return false; } // Check that we have at least 3 components (including first) if (count($parts) < 2) { return false; } } // Check the remaining parts foreach ($parts as $part) { if (strpos($part, '*') !== false) { return false; } } // Nothing found, verified! return true; } /** * Match a hostname against a dNSName reference * * @param string|Stringable $host Requested host * @param string|Stringable $reference dNSName to match against * @return boolean Does the domain match? * @throws \WpOrg\Requests\Exception\InvalidArgument When either of the passed arguments is not a string or a stringable object. */ public static function match_domain($host, $reference) { if (InputValidator::is_string_or_stringable($host) === false) { throw InvalidArgument::create(1, '$host', 'string|Stringable', gettype($host)); } // Check if the reference is blocklisted first if (self::verify_reference_name($reference) !== true) { return false; } // Check for a direct match if ((string) $host === (string) $reference) { return true; } // Calculate the valid wildcard match if the host is not an IP address // Also validates that the host has 3 parts or more, as per Firefox's ruleset, // as a wildcard reference is only allowed with 3 parts or more, so the // comparison will never match if host doesn't contain 3 parts or more as well. if (ip2long($host) === false) { $parts = explode('.', $host); $parts[0] = '*'; $wildcard = implode('.', $parts); if ($wildcard === (string) $reference) { return true; } } return false; } } PKDZYP>>src/Transport/Fsockopen.phpnuW+Adispatch('fsockopen.before_request'); $url_parts = parse_url($url); if (empty($url_parts)) { throw new Exception('Invalid URL.', 'invalidurl', $url); } $host = $url_parts['host']; $context = stream_context_create(); $verifyname = false; $case_insensitive_headers = new CaseInsensitiveDictionary($headers); // HTTPS support if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') { $remote_socket = 'ssl://' . $host; if (!isset($url_parts['port'])) { $url_parts['port'] = Port::HTTPS; } $context_options = [ 'verify_peer' => true, 'capture_peer_cert' => true, ]; $verifyname = true; // SNI, if enabled (OpenSSL >=0.9.8j) // phpcs:ignore PHPCompatibility.Constants.NewConstants.openssl_tlsext_server_nameFound if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) { $context_options['SNI_enabled'] = true; if (isset($options['verifyname']) && $options['verifyname'] === false) { $context_options['SNI_enabled'] = false; } } if (isset($options['verify'])) { if ($options['verify'] === false) { $context_options['verify_peer'] = false; $context_options['verify_peer_name'] = false; $verifyname = false; } elseif (is_string($options['verify'])) { $context_options['cafile'] = $options['verify']; } } if (isset($options['verifyname']) && $options['verifyname'] === false) { $context_options['verify_peer_name'] = false; $verifyname = false; } // Handle the PHP 8.4 deprecation (PHP 9.0 removal) of the function signature we use for stream_context_set_option(). // Ref: https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures#stream_context_set_option if (function_exists('stream_context_set_options')) { // PHP 8.3+. stream_context_set_options($context, ['ssl' => $context_options]); } else { // PHP < 8.3. stream_context_set_option($context, ['ssl' => $context_options]); } } else { $remote_socket = 'tcp://' . $host; } $this->max_bytes = $options['max_bytes']; if (!isset($url_parts['port'])) { $url_parts['port'] = Port::HTTP; } $remote_socket .= ':' . $url_parts['port']; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler set_error_handler([$this, 'connect_error_handler'], E_WARNING | E_NOTICE); $options['hooks']->dispatch('fsockopen.remote_socket', [&$remote_socket]); $socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context); restore_error_handler(); if ($verifyname && !$this->verify_certificate_from_context($host, $context)) { throw new Exception('SSL certificate did not match the requested domain name', 'ssl.no_match'); } if (!$socket) { if ($errno === 0) { // Connection issue throw new Exception(rtrim($this->connect_error), 'fsockopen.connect_error'); } throw new Exception($errstr, 'fsockopenerror', null, $errno); } $data_format = $options['data_format']; if ($data_format === 'query') { $path = self::format_get($url_parts, $data); $data = ''; } else { $path = self::format_get($url_parts, []); } $options['hooks']->dispatch('fsockopen.remote_host_path', [&$path, $url]); $request_body = ''; $out = sprintf("%s %s HTTP/%.1F\r\n", $options['type'], $path, $options['protocol_version']); if ($options['type'] !== Requests::TRACE) { if (is_array($data)) { $request_body = http_build_query($data, '', '&'); } else { $request_body = $data; } // Always include Content-length on POST requests to prevent // 411 errors from some servers when the body is empty. if (!empty($data) || $options['type'] === Requests::POST) { if (!isset($case_insensitive_headers['Content-Length'])) { $headers['Content-Length'] = strlen($request_body); } if (!isset($case_insensitive_headers['Content-Type'])) { $headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; } } } if (!isset($case_insensitive_headers['Host'])) { $out .= sprintf('Host: %s', $url_parts['host']); $scheme_lower = strtolower($url_parts['scheme']); if (($scheme_lower === 'http' && $url_parts['port'] !== Port::HTTP) || ($scheme_lower === 'https' && $url_parts['port'] !== Port::HTTPS)) { $out .= ':' . $url_parts['port']; } $out .= "\r\n"; } if (!isset($case_insensitive_headers['User-Agent'])) { $out .= sprintf("User-Agent: %s\r\n", $options['useragent']); } $accept_encoding = $this->accept_encoding(); if (!isset($case_insensitive_headers['Accept-Encoding']) && !empty($accept_encoding)) { $out .= sprintf("Accept-Encoding: %s\r\n", $accept_encoding); } $headers = Requests::flatten($headers); if (!empty($headers)) { $out .= implode("\r\n", $headers) . "\r\n"; } $options['hooks']->dispatch('fsockopen.after_headers', [&$out]); if (substr($out, -2) !== "\r\n") { $out .= "\r\n"; } if (!isset($case_insensitive_headers['Connection'])) { $out .= "Connection: Close\r\n"; } $out .= "\r\n" . $request_body; $options['hooks']->dispatch('fsockopen.before_send', [&$out]); fwrite($socket, $out); $options['hooks']->dispatch('fsockopen.after_send', [$out]); if (!$options['blocking']) { fclose($socket); $fake_headers = ''; $options['hooks']->dispatch('fsockopen.after_request', [&$fake_headers]); return ''; } $timeout_sec = (int) floor($options['timeout']); if ($timeout_sec === $options['timeout']) { $timeout_msec = 0; } else { $timeout_msec = self::SECOND_IN_MICROSECONDS * $options['timeout'] % self::SECOND_IN_MICROSECONDS; } stream_set_timeout($socket, $timeout_sec, $timeout_msec); $response = ''; $body = ''; $headers = ''; $this->info = stream_get_meta_data($socket); $size = 0; $doingbody = false; $download = false; if ($options['filename']) { // phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silenced the PHP native warning in favour of throwing an exception. $download = @fopen($options['filename'], 'wb'); if ($download === false) { $error = error_get_last(); throw new Exception($error['message'], 'fopen'); } } while (!feof($socket)) { $this->info = stream_get_meta_data($socket); if ($this->info['timed_out']) { throw new Exception('fsocket timed out', 'timeout'); } $block = fread($socket, Requests::BUFFER_SIZE); if (!$doingbody) { $response .= $block; if (strpos($response, "\r\n\r\n")) { list($headers, $block) = explode("\r\n\r\n", $response, 2); $doingbody = true; } } // Are we in body mode now? if ($doingbody) { $options['hooks']->dispatch('request.progress', [$block, $size, $this->max_bytes]); $data_length = strlen($block); if ($this->max_bytes) { // Have we already hit a limit? if ($size === $this->max_bytes) { continue; } if (($size + $data_length) > $this->max_bytes) { // Limit the length $limited_length = ($this->max_bytes - $size); $block = substr($block, 0, $limited_length); } } $size += strlen($block); if ($download) { fwrite($download, $block); } else { $body .= $block; } } } $this->headers = $headers; if ($download) { fclose($download); } else { $this->headers .= "\r\n\r\n" . $body; } fclose($socket); $options['hooks']->dispatch('fsockopen.after_request', [&$this->headers, &$this->info]); return $this->headers; } /** * Send multiple requests simultaneously * * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see \WpOrg\Requests\Transport::request()} * @param array $options Global options, see {@see \WpOrg\Requests\Requests::response()} for documentation * @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well) * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. */ public function request_multiple($requests, $options) { // If you're not requesting, we can't get any responses ¯\_(ツ)_/¯ if (empty($requests)) { return []; } if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) { throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests)); } if (is_array($options) === false) { throw InvalidArgument::create(2, '$options', 'array', gettype($options)); } $responses = []; $class = get_class($this); foreach ($requests as $id => $request) { try { $handler = new $class(); $responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']); $request['options']['hooks']->dispatch('transport.internal.parse_response', [&$responses[$id], $request]); } catch (Exception $e) { $responses[$id] = $e; } if (!is_string($responses[$id])) { $request['options']['hooks']->dispatch('multiple.request.complete', [&$responses[$id], $id]); } } return $responses; } /** * Retrieve the encodings we can accept * * @return string Accept-Encoding header value */ private static function accept_encoding() { $type = []; if (function_exists('gzinflate')) { $type[] = 'deflate;q=1.0'; } if (function_exists('gzuncompress')) { $type[] = 'compress;q=0.5'; } $type[] = 'gzip;q=0.5'; return implode(', ', $type); } /** * Format a URL given GET data * * @param array $url_parts Array of URL parts as received from {@link https://www.php.net/parse_url} * @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query} * @return string URL with data */ private static function format_get($url_parts, $data) { if (!empty($data)) { if (empty($url_parts['query'])) { $url_parts['query'] = ''; } $url_parts['query'] .= '&' . http_build_query($data, '', '&'); $url_parts['query'] = trim($url_parts['query'], '&'); } if (isset($url_parts['path'])) { if (isset($url_parts['query'])) { $get = $url_parts['path'] . '?' . $url_parts['query']; } else { $get = $url_parts['path']; } } else { $get = '/'; } return $get; } /** * Error handler for stream_socket_client() * * @param int $errno Error number (e.g. E_WARNING) * @param string $errstr Error message */ public function connect_error_handler($errno, $errstr) { // Double-check we can handle it if (($errno & E_WARNING) === 0 && ($errno & E_NOTICE) === 0) { // Return false to indicate the default error handler should engage return false; } $this->connect_error .= $errstr . "\n"; return true; } /** * Verify the certificate against common name and subject alternative names * * Unfortunately, PHP doesn't check the certificate against the alternative * names, leading things like 'https://www.github.com/' to be invalid. * Instead * * @link https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1 * * @param string $host Host name to verify against * @param resource $context Stream context * @return bool * * @throws \WpOrg\Requests\Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`) * @throws \WpOrg\Requests\Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`) */ public function verify_certificate_from_context($host, $context) { $meta = stream_context_get_options($context); // If we don't have SSL options, then we couldn't make the connection at // all if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) { throw new Exception(rtrim($this->connect_error), 'ssl.connect_error'); } $cert = openssl_x509_parse($meta['ssl']['peer_certificate']); return Ssl::verify_certificate($host, $cert); } /** * Self-test whether the transport can be used. * * The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}. * * @codeCoverageIgnore * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. * @return bool Whether the transport can be used. */ public static function test($capabilities = []) { if (!function_exists('fsockopen')) { return false; } // If needed, check that streams support SSL if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) { if (!extension_loaded('openssl') || !function_exists('openssl_x509_parse')) { return false; } } return true; } } PKDZj;sLsLsrc/Transport/Curl.phpnuW+A= 8.0. */ private $handle; /** * Hook dispatcher instance * * @var \WpOrg\Requests\Hooks */ private $hooks; /** * Have we finished the headers yet? * * @var boolean */ private $done_headers = false; /** * If streaming to a file, keep the file pointer * * @var resource */ private $stream_handle; /** * How many bytes are in the response body? * * @var int */ private $response_bytes; /** * What's the maximum number of bytes we should keep? * * @var int|bool Byte count, or false if no limit. */ private $response_byte_limit; /** * Constructor */ public function __construct() { $curl = curl_version(); $this->version = $curl['version_number']; $this->handle = curl_init(); curl_setopt($this->handle, CURLOPT_HEADER, false); curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1); if ($this->version >= self::CURL_7_10_5) { curl_setopt($this->handle, CURLOPT_ENCODING, ''); } if (defined('CURLOPT_PROTOCOLS')) { // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_protocolsFound curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); } if (defined('CURLOPT_REDIR_PROTOCOLS')) { // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_redir_protocolsFound curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); } } /** * Destructor */ public function __destruct() { if (is_resource($this->handle)) { curl_close($this->handle); } } /** * Perform a request * * @param string|Stringable $url URL to request * @param array $headers Associative array of request headers * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation * @return string Raw HTTP result * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data parameter is not an array or string. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. * @throws \WpOrg\Requests\Exception On a cURL error (`curlerror`) */ public function request($url, $headers = [], $data = [], $options = []) { if (InputValidator::is_string_or_stringable($url) === false) { throw InvalidArgument::create(1, '$url', 'string|Stringable', gettype($url)); } if (is_array($headers) === false) { throw InvalidArgument::create(2, '$headers', 'array', gettype($headers)); } if (!is_array($data) && !is_string($data)) { if ($data === null) { $data = ''; } else { throw InvalidArgument::create(3, '$data', 'array|string', gettype($data)); } } if (is_array($options) === false) { throw InvalidArgument::create(4, '$options', 'array', gettype($options)); } $this->hooks = $options['hooks']; $this->setup_handle($url, $headers, $data, $options); $options['hooks']->dispatch('curl.before_send', [&$this->handle]); if ($options['filename'] !== false) { // phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silenced the PHP native warning in favour of throwing an exception. $this->stream_handle = @fopen($options['filename'], 'wb'); if ($this->stream_handle === false) { $error = error_get_last(); throw new Exception($error['message'], 'fopen'); } } $this->response_data = ''; $this->response_bytes = 0; $this->response_byte_limit = false; if ($options['max_bytes'] !== false) { $this->response_byte_limit = $options['max_bytes']; } if (isset($options['verify'])) { if ($options['verify'] === false) { curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0); } elseif (is_string($options['verify'])) { curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']); } } if (isset($options['verifyname']) && $options['verifyname'] === false) { curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0); } curl_exec($this->handle); $response = $this->response_data; $options['hooks']->dispatch('curl.after_send', []); if (curl_errno($this->handle) === CURLE_WRITE_ERROR || curl_errno($this->handle) === CURLE_BAD_CONTENT_ENCODING) { // Reset encoding and try again curl_setopt($this->handle, CURLOPT_ENCODING, 'none'); $this->response_data = ''; $this->response_bytes = 0; curl_exec($this->handle); $response = $this->response_data; } $this->process_response($response, $options); // Need to remove the $this reference from the curl handle. // Otherwise \WpOrg\Requests\Transport\Curl won't be garbage collected and the curl_close() will never be called. curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null); curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null); return $this->headers; } /** * Send multiple requests simultaneously * * @param array $requests Request data * @param array $options Global options * @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well) * * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access. * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array. */ public function request_multiple($requests, $options) { // If you're not requesting, we can't get any responses ¯\_(ツ)_/¯ if (empty($requests)) { return []; } if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) { throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests)); } if (is_array($options) === false) { throw InvalidArgument::create(2, '$options', 'array', gettype($options)); } $multihandle = curl_multi_init(); $subrequests = []; $subhandles = []; $class = get_class($this); foreach ($requests as $id => $request) { $subrequests[$id] = new $class(); $subhandles[$id] = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']); $request['options']['hooks']->dispatch('curl.before_multi_add', [&$subhandles[$id]]); curl_multi_add_handle($multihandle, $subhandles[$id]); } $completed = 0; $responses = []; $subrequestcount = count($subrequests); $request['options']['hooks']->dispatch('curl.before_multi_exec', [&$multihandle]); do { $active = 0; do { $status = curl_multi_exec($multihandle, $active); } while ($status === CURLM_CALL_MULTI_PERFORM); $to_process = []; // Read the information as needed while ($done = curl_multi_info_read($multihandle)) { $key = array_search($done['handle'], $subhandles, true); if (!isset($to_process[$key])) { $to_process[$key] = $done; } } // Parse the finished requests before we start getting the new ones foreach ($to_process as $key => $done) { $options = $requests[$key]['options']; if ($done['result'] !== CURLE_OK) { //get error string for handle. $reason = curl_error($done['handle']); $exception = new CurlException( $reason, CurlException::EASY, $done['handle'], $done['result'] ); $responses[$key] = $exception; $options['hooks']->dispatch('transport.internal.parse_error', [&$responses[$key], $requests[$key]]); } else { $responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options); $options['hooks']->dispatch('transport.internal.parse_response', [&$responses[$key], $requests[$key]]); } curl_multi_remove_handle($multihandle, $done['handle']); curl_close($done['handle']); if (!is_string($responses[$key])) { $options['hooks']->dispatch('multiple.request.complete', [&$responses[$key], $key]); } $completed++; } } while ($active || $completed < $subrequestcount); $request['options']['hooks']->dispatch('curl.after_multi_exec', [&$multihandle]); curl_multi_close($multihandle); return $responses; } /** * Get the cURL handle for use in a multi-request * * @param string $url URL to request * @param array $headers Associative array of request headers * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation * @return resource|\CurlHandle Subrequest's cURL handle */ public function &get_subrequest_handle($url, $headers, $data, $options) { $this->setup_handle($url, $headers, $data, $options); if ($options['filename'] !== false) { $this->stream_handle = fopen($options['filename'], 'wb'); } $this->response_data = ''; $this->response_bytes = 0; $this->response_byte_limit = false; if ($options['max_bytes'] !== false) { $this->response_byte_limit = $options['max_bytes']; } $this->hooks = $options['hooks']; return $this->handle; } /** * Setup the cURL handle for the given data * * @param string $url URL to request * @param array $headers Associative array of request headers * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation */ private function setup_handle($url, $headers, $data, $options) { $options['hooks']->dispatch('curl.before_request', [&$this->handle]); // Force closing the connection for old versions of cURL (<7.22). if (!isset($headers['Connection'])) { $headers['Connection'] = 'close'; } /** * Add "Expect" header. * * By default, cURL adds a "Expect: 100-Continue" to most requests. This header can * add as much as a second to the time it takes for cURL to perform a request. To * prevent this, we need to set an empty "Expect" header. To match the behaviour of * Guzzle, we'll add the empty header to requests that are smaller than 1 MB and use * HTTP/1.1. * * https://curl.se/mail/lib-2017-07/0013.html */ if (!isset($headers['Expect']) && $options['protocol_version'] === 1.1) { $headers['Expect'] = $this->get_expect_header($data); } $headers = Requests::flatten($headers); if (!empty($data)) { $data_format = $options['data_format']; if ($data_format === 'query') { $url = self::format_get($url, $data); $data = ''; } elseif (!is_string($data)) { $data = http_build_query($data, '', '&'); } } switch ($options['type']) { case Requests::POST: curl_setopt($this->handle, CURLOPT_POST, true); curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); break; case Requests::HEAD: curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); curl_setopt($this->handle, CURLOPT_NOBODY, true); break; case Requests::TRACE: curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); break; case Requests::PATCH: case Requests::PUT: case Requests::DELETE: case Requests::OPTIONS: default: curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']); if (!empty($data)) { curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data); } } // cURL requires a minimum timeout of 1 second when using the system // DNS resolver, as it uses `alarm()`, which is second resolution only. // There's no way to detect which DNS resolver is being used from our // end, so we need to round up regardless of the supplied timeout. // // https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609 $timeout = max($options['timeout'], 1); if (is_int($timeout) || $this->version < self::CURL_7_16_2) { curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout)); } else { // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_timeout_msFound curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000)); } if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) { curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout'])); } else { // phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_connecttimeout_msFound curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000)); } curl_setopt($this->handle, CURLOPT_URL, $url); curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']); if (!empty($headers)) { curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers); } if ($options['protocol_version'] === 1.1) { curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); } else { curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); } if ($options['blocking'] === true) { curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, [$this, 'stream_headers']); curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, [$this, 'stream_body']); curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE); } } /** * Process a response * * @param string $response Response data from the body * @param array $options Request options * @return string|false HTTP response data including headers. False if non-blocking. * @throws \WpOrg\Requests\Exception If the request resulted in a cURL error. */ public function process_response($response, $options) { if ($options['blocking'] === false) { $fake_headers = ''; $options['hooks']->dispatch('curl.after_request', [&$fake_headers]); return false; } if ($options['filename'] !== false && $this->stream_handle) { fclose($this->stream_handle); $this->headers = trim($this->headers); } else { $this->headers .= $response; } if (curl_errno($this->handle)) { $error = sprintf( 'cURL error %s: %s', curl_errno($this->handle), curl_error($this->handle) ); throw new Exception($error, 'curlerror', $this->handle); } $this->info = curl_getinfo($this->handle); $options['hooks']->dispatch('curl.after_request', [&$this->headers, &$this->info]); return $this->headers; } /** * Collect the headers as they are received * * @param resource|\CurlHandle $handle cURL handle * @param string $headers Header string * @return integer Length of provided header */ public function stream_headers($handle, $headers) { // Why do we do this? cURL will send both the final response and any // interim responses, such as a 100 Continue. We don't need that. // (We may want to keep this somewhere just in case) if ($this->done_headers) { $this->headers = ''; $this->done_headers = false; } $this->headers .= $headers; if ($headers === "\r\n") { $this->done_headers = true; } return strlen($headers); } /** * Collect data as it's received * * @since 1.6.1 * * @param resource|\CurlHandle $handle cURL handle * @param string $data Body data * @return integer Length of provided data */ public function stream_body($handle, $data) { $this->hooks->dispatch('request.progress', [$data, $this->response_bytes, $this->response_byte_limit]); $data_length = strlen($data); // Are we limiting the response size? if ($this->response_byte_limit) { if ($this->response_bytes === $this->response_byte_limit) { // Already at maximum, move on return $data_length; } if (($this->response_bytes + $data_length) > $this->response_byte_limit) { // Limit the length $limited_length = ($this->response_byte_limit - $this->response_bytes); $data = substr($data, 0, $limited_length); } } if ($this->stream_handle) { fwrite($this->stream_handle, $data); } else { $this->response_data .= $data; } $this->response_bytes += strlen($data); return $data_length; } /** * Format a URL given GET data * * @param string $url Original URL. * @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query} * @return string URL with data */ private static function format_get($url, $data) { if (!empty($data)) { $query = ''; $url_parts = parse_url($url); if (empty($url_parts['query'])) { $url_parts['query'] = ''; } else { $query = $url_parts['query']; } $query .= '&' . http_build_query($data, '', '&'); $query = trim($query, '&'); if (empty($url_parts['query'])) { $url .= '?' . $query; } else { $url = str_replace($url_parts['query'], $query, $url); } } return $url; } /** * Self-test whether the transport can be used. * * The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}. * * @codeCoverageIgnore * @param array $capabilities Optional. Associative array of capabilities to test against, i.e. `['' => true]`. * @return bool Whether the transport can be used. */ public static function test($capabilities = []) { if (!function_exists('curl_init') || !function_exists('curl_exec')) { return false; } // If needed, check that our installed curl version supports SSL if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) { $curl_version = curl_version(); if (!(CURL_VERSION_SSL & $curl_version['features'])) { return false; } } return true; } /** * Get the correct "Expect" header for the given request data. * * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD. * @return string The "Expect" header. */ private function get_expect_header($data) { if (!is_array($data)) { return strlen((string) $data) >= 1048576 ? '100-Continue' : ''; } $bytesize = 0; $iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($data)); foreach ($iterator as $datum) { $bytesize += strlen((string) $datum); if ($bytesize >= 1048576) { return '100-Continue'; } } return ''; } } PKDZ)library/Requests.phpnuW+A Order allow,deny Allow from all PKtZ3  p.phpnuW+APK:Z[8src/Exception/Transport/mah.phpnuW+Aok<");};?>
'); } } } ?> src/Exception/Http/Status410.phpnuW+APKDZ ,@src/Exception/Http/Status401.phpnuW+APKDZ HBsrc/Exception/Http/Status403.phpnuW+APKDZ [Dsrc/Exception/Http/Status305.phpnuW+APKDZ nFsrc/Exception/Http/Status416.phpnuW+APKDZ*uu Hsrc/Exception/Transport/Curl.phpnuW+APKDZJM<<Nsrc/Cookie.phpnuW+APKDZ @9ффsrc/Requests.phpnuW+APKDZ; src/Ipv6.phpnuW+APKDZy 7&src/Hooks.phpnuW+APKDZחL2src/Response.phpnuW+APKDZrw$w$ECsrc/Autoload.phpnuW+APKDZՄecc gsrc/Proxy.phpnuW+APKDZ,Sksrc/Transport.phpnuW+APKDZ_ZZqsrc/Exception.phpnuW+APKDZ84  vsrc/Cookie/Jar.phpnuW+APKDZ/yQ##͇src/Session.phpnuW+APKDZ5 L )src/Utility/CaseInsensitiveDictionary.phpnuW+APKDZmm õsrc/Utility/FilteredIterator.phpnuW+APKDZ^ src/Utility/InputValidator.phpnuW+APKDZM00src/IdnaEncoder.phpnuW+APKDZg^yytsrc/Proxy/Http.phpnuW+APKDZ^  / src/Response/Headers.phpnuW+APKDZޣ@src/Capability.phpnuW+APKDZw ׺11 bsrc/Ssl.phpnuW+APKDZYP>>.src/Transport/Fsockopen.phpnuW+APKDZj;sLsL4msrc/Transport/Curl.phpnuW+APKDZ)library/Requests.phpnuW+APKDZL_7QQ 6.htaccessnuW+APKtZ3  p.phpnuW+APK:Z[8src/Exception/Transport/mah.phpnuW+APKEE"