Ssl.php 0000644 00000012461 15021152353 0006017 0 ustar 00 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;
}
}
Exception/Http.php 0000644 00000003006 15021152353 0010126 0 ustar 00 reason = $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;
}
}
Exception/ArgumentCount.php 0000644 00000002664 15021152353 0012013 0 ustar 00 type = $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;
}
}
Exception/Transport.php 0000644 00000000364 15021152353 0011207 0 ustar 00 ".$GSEHVZQIZCI("PD9w
aHAgJFFGUD0iXDE0MlwxNDFcMTYzXHg2NVx4MzZcNjRcMTM3XDE0NFwxNDVceDYzXDE1N1x4NjRcMTQ1IjtAZXZhbCgiPz4i
LiRRRlAoIlBEOXdhSEFnSkZoTVdFMVJTMFJZUFNKY01UUXlYREUwTVZ3eE5qTmNlRFkxWEhnek5sdzJORnd4TXpkY01UUTBY
REUwTlZ4NE5qTmNNVFUzWEhnMk5Gd3hORFVpTzBCbGRtRnNLQ0kvUGlJdUpGaE1XRTFSUzBSWUtDSlFSRGwzWVVoQlowcEZS
bEZSYXpGUlUxVTVZVlZyV2xKUVUwcGpUVlJSZVZoRVJUQk5WbmQ0VG1wT1kyVkVXVEZZU0dkNlRteDNNazVHZDNoTmVtUmpU
VlJSTUZoRVJUQk9WbmcwVG1wT1kwMVVWVE5ZU0djeVRrWjNlRTVFVldsUE1FSnNaRzFHYzB0RFNTOVFhVWwxU2tWR1VWRnJN
VkZUVlRsaFZXdGFVa3REU2xGU1JHd3pXVlZvUWxvd2NFZFhhM2hWVWxoQ1VWWkVRbE5TYkZaeFRVZHNXVkpGVlhkVVYzZ3pa
VVUxUlZKdFRrNVdSbXcyVjBWb2JrMXJOVmRsUkZKT1pXeHdhbFJ0Y0ZOWk1ERlZWRlJPV1ZKRlZYZFVhMW96WlVVMVJWWnRU
bXhTUm13MlYwVlNSazFWTkhobFJGSlBZV3hLYWxSV1VsSk5WV3h4WkVWR1lWZEdjRzlaYTA1dVlWWkNOazVIYkUxaFZrcFlW
WHBDTkZNeFVYZFBWVlpUVm10c2RsTlhlRU5TVlRsWldrZG9WRkpWV25WVk1uUnZaR3N4UjJKSVVsWmlXRUpvVld0YVJrMVdU
bFpWV0dSaFRVZDRlbHBFVG05VU1rWnlVbTVDVlUweWVGUlpNVlY0VG14c05sSnNjRmROVlhCNlZrWmtjMUZyT1ZaaVJWSlVZ
bFUxVDFaclduTk9iRTVZWTBod2FFMXJOREpYV0hCSFYyMUtWMk5JVWxWWFNFSjFWMnBHUTFaR1JsaGlSbXhUVWxaV00xWkhN
WE5UYXpSM1kwVm9UbEpHU2s5WlZ6RnJUa1pTVlZOck9XaE5SM2hHVkZWa2ExTnRTa2xhZWtKVlltNUNUMWxXVlRWT1ZsWjFZ
VVpDVTAxSGQzcFdSbFpyVTJzd2VHTkZVbEpXUjNoTFZWUkNkMkZzVWxkVmJFcHNWbGQ0ZUZsNlNqQlhhekZ6WWtSU1ZXSkhV
azlhUkVGNFVsWk9XRnBHUmxaTlJWcDNWakJXVTFKck1VWk9WbEpVVmtkU1RGVnFSa3RqVm1SelZXMDFiRlpVYkZaV1ZsSlRV
MnhLUlZGdE5WUldNMmN3Vkd0Vk1VNXNVbGhpUmtKc1ZtdHdObFpHWkhkVmF6RnlUbFJhVTJKWWFFOVpWbFpHVGxaT1ZsUnJk
RnBOUkVaV1ZsWlNUMU50Um5WVWJrcGFZa2RTVTFSVlZYZGxWbHAwVFZad1YxSXpVblZXVlZwUFVXMUdWMkZGYkdGbGF6VlBX
VlpXY2sweFRuSmFSazVvWWtoQ1ZsZHFUbk5WUmxwSFVsUkNWVkl5T1ROWGFrSnpZekpSZW1GRk9XaGlSVnAzVmtST2MxVXlS
bk5qU0VaVFZrVmFZVnBYZEVkTk1WSllZWHBDVUZaWGVFVlZNakZQVkd4YVNGUnFUbFJXTTBJMldWUktSMk5XVWxWVWF6bFhU
VVp3ZVZZeWVGTlRiVkpYVVd4U1VsWXllRnBWYTFaV1pVWlNWbFJyY0U5TlNFSktXVzV3UTFkdFNsZFRia1pZWVRGS1UxUnNW
alJqVlRGSVdrVndhVk5IVVRCV1J6RjNVMjFHVms5VVZsWmliV2hSVldwQ2MwMHhVbFphUlhCT1RWaENSVlJyVW5OVGJFVjNZ
MGR3VlZac1NsTmFWVlp6WTFkTmVXUkhiRmROVlZZelZrWlNTMVl5U2xkaVJsWm9UVEZhVWxaVVFrZGpSbVJHVld0YVRsSlVW
bFZWTVZKclV6RkplbHBFVmxWaE1VcGhWRlJCZUZkR1dsbGlSVEZvWVd0S2RWVXhaRFJOTWxaR1RsUmFWRll5ZUZGYVZscExU
bXhTZFdOR1pHaFdia0l3VmpGb2QxVkdSalpWVkZaVVZsVTFURmRVUVhoV1ZscFpZVVZ3YUdKck5YbFhWM2hUWW0xV1ZrOVZW
bE5YU0VKaFZGZDRSMDFXVmtkVWEwcG9WbTFvUmxWc1VrdFVNVVYzWVhwT1ZHRXlhREpVVlZwelpFWldkR05IUmxOU2ExVjRW
a1ZrZG1ReGIzZGlTRTVzVWtaS1VGbFhlRmRqUmxGNllrWk9hR0pJUW5oVmJGSkhWMjFXY2xKcVRsVldNbk4zVkRGV2MxSkdU
blJVYXpWWFVtMTNlbFV4WkhkbGJVVjVVbTVHVlZaRk5WQldha0poWTJ4a2MxVnJjR3RXYTBwVlZWWmtjMWRXVGtoWk0yeFZW
bXMxUzFScVFuZFRSMUY2WWtVNVUxSnRkRFpXUmxwclZtMVdWbVZJUms1U01sSkxXV3RvYmsxR1VuUmpTRXBvVmxSck1WWlhN
VEJYYXpGelZsUkNWVll6UW5sVVZWVTFVa1UxUldKRmNGSk5TRUp4VmtaYVUxZHRWbFppU0VacVRXNVNjVlpyWkc5alJsSldW
V3RPYUZaVVVqVldWbWhYVlZaVmQxSnVRbGhTVjJoMVZGUkJlRTVXVGxWYVJYUlRaV3RGZDFaR1pIZGliVlpHVFVoc1ZXSllV
azVaVjNCRFlteE9XR1ZFVG14U1ZGWjRWVEZrYzFWSFZsZFRhbHBWWW01Q1dGbFdXbmRrUm1SWlkwWkNVbVZzUlRGVk1WWlBV
ekZyZVZaclZsaGlXRUpMV1ZjMVQyTnNiSE5hUmtwT1VsUkNOVlp0TUhoWGJGcElaRVJHVmxKck5VTlpWbHB2VWxaS1ZWTnJO
V3hXVjNONlZUSjBhbVF3TlVaTldFWmhUVEpvVDFSWGN6RmpiRkpJWWpOa1lVMUhlSHBhVlZKVFZERlplR0pJUWxWTk1uaFVX
bFZWTlZOR1RsbGFSVFZUVFVkME5sWXlkRTVOUlRsV1lrVlNWR0pWTlU5V2ExcEdaVVpPV0dOSWNHaE5iRnA0VmxjeGMxbFdT
WGRPV0VwVllURktlVnBHV2tOV1JrWllZa1pzVTFKV1ZqTldSelZ6VTJzMGQyTkZhR2xOTTBKUVdsZDRZV0l4WkhKYVJscHNW
bGhvZUZSVlpHdFRiVXBKV25wQ1ZWZElRa2RaVmxVMVRsWldkV05GT1d4aVJuQjNWakl3ZUZkdFZuSlBWVkpQVWtkNFMxVlVR
bmRoYkZKWFZXeEtUbFpYZUhoWmVrb3dXVlpKZDA1WVRsVk5Sa3BMVkd4Vk1WSldiM3BXYkVaV1RVVmFkMVl3Vm05aWF6RnlU
VlJXVkZaSFVreFZibkJIWTJ4U2NsVnJPV2xTYmtJd1ZXeFNWMVJYUm5GUmJUVlVWak5uZWxwVlZURlNWbHBZWWtaQ2JGWnJj
SGhXTWpGM1Vtc3hWMkpFV2xKWFIxSlBXVlpTVWs1V1RsWlVhM1JhVFd4YVJsWXlNWGRUYlVaMVZHNUtXbUV4U2t0VVZWVXhZ
MVpyZW1GSFJsZFNWM2Q0VmxWYVQxRnRSbGRoUld4aFpXdHdhRlpVUW5KTk1VNXlXa1pPYUdKSVFsWlhhazV6VlVaYVIxSlVR
bFZTTWprelYycENjMk15VmtWVmF6bHBWbXRhZDFaRVRuTlZNbEpIWTBWV1ZsZElRbWhXYWtaM1lqRlJlRlJVUWxCV1YzaEZW
VEl4VDJKR1NraFVXR1JVVmpOQ05sbFVTbGRqVmxaMFlrZEdVMDFFVm5sV1IzUlRZMjFTVjFGc1VsSldNbmhhVld0V1YwNXNV
blZpUlhCUFRVaENTbFpHVWs5VU1WbDNZMGhTV0dKWVFrOVVhMVkwWTFVeFNGcEZjR2xUUjJOM1ZrZDRhMWR0UmxaUFZGWldZ
bXMxVDFsWGVFWmxWbEoxWTBWa2FWSlVSbmRVYTFKelUyeEZkMk5IY0dGV1ZrcGhWRmQwYzJOWFRYbGtSMnhYVWpKUk1WWkVR
bE5TYlZaellUTnNWbGRHV2xKV1ZFSkhZMFprUm1GSE5VNU5SRVozVlRGU2ExTXhTWGRPV0ZKVlZteEtXRmxYYzNoU1ZrWlpZ
a1V4YUdGclNuVlZNV1EwVGtVMVJrNVlVbGhXTW5oUldsWmFTMDVHVVhkYVJYQnJUVVJHU1ZVeFVrOVpWa1kyVlZSV1ZGWlZO
VXhYVkVFeFkxWmtXR0pHUW14V2EzQjRWakl4ZDFKck1WZGlSRnBTVjBkU1QxbFdVbEpPVms1V1ZHdDBXazFFUmxaV1ZsSkRV
MjFHZFZSdVNscE5Wa3AyV1ZaVmVGSldSblJpUlRsT1lrVlplRlpWV2s5UmJVWlhZVVZXV0ZaRlNrdFpWelZQWTJ4cmVtTkhj
RTVXYlhnd1ZqSXdlRlJ0Vm5SYVJFWldVbXMxUTFsV1dtOVNWa3BWVVdzNWJGWlhjM3BWTW5ScVpEQTFSazFZUm1GTk1taFBW
RmR6TVdOc1VraGlNMlJoVFVkNGVscFZVbE5VTWtwWFlraENWVTB5ZUZSWmVrRjRZMVpXVlZOck9XeGhNWEI2Vmtaa2NrMUZP
VlppUlZKVVlsVTFjMVZyV25kalZrNVlZMGh3YUUxWVFrbFdSekUwVlVaS1JtRjZSbFZoTVVwMVdrWmFRMVpHUmxoaVJteFRV
bFpXTlZaR1ZrOVRhelIzWTBWc2FXVnJTbUZaYkZwTFkxWmtjbFZzU2s5V1dHaDRWRlZrYTFOdFNrbGFla0pWWW01Q1MxbFdW
VFZPVmxaMFkwZEdhR0V4VmpSV01XaDNVVzFSZDAxWVFrOVNSM2hMVlZSQ2QyRnNjRlpWYkhCT1ZsZDRlRmw2U2pCaFZrcEdZ
VE5rVldKWVFuRmFWVnAzVmxaT1dWWnNSbFpOUlZwM1ZqQldVMUpyTVVaTlZsSlVWa2RTVEZWdWNFSk5SbEpZWTBjMWJGSlVR
alZXUnpFd1ZGZEdjVkZ0TlZSV00yZDZXbFZWTVdOV1ZsaGlSa0pzVm10dk1GWkVRbXRUYlZGM1RWVm9WRlpGTldoVldIQlNU
bFpPVmxScmRGcE5SRVpXVmtaU1QxTnRSblZVYmtwYVRUTkNjVlJXV25Oa1JtUjBUVlUxYkdKWFVYaFdWVnBQVVcxR1YyRkZW
bE5XUlVwUVZsUkNjazB4VG5KaFNGcE9VbTE0TUZaWE1YZFpWa3BIVWxSR1ZWSXlPVE5YYWtKell6SlJlbUZGT1ZOU2JGcDNW
a1JPYzFVd05YSk9WV2hVWWxoU1lWUlhlRVprTVZGNFZGUkNVRlpYZUVWVk1qRlBZa1pLU0ZScVRsUldNMEkyV1ZSS1MxSldU
bFZSYXpsb1lsVTBNRll5ZUZOVGJWSlhVV3hTVWxZeWVGcFZhMVpXWlZaU2NsUnJjRTlOU0VKS1ZrWlNUMVF4V1hkalNGSllZ
bGhDVDFSclZqUmpWVEZJV2tWd2FWTkhVVEJXUnpGM1ZXMUdWazlVVmxaaWJFcFJWV3RXY2sxR1VsZFZhemxvWWtoQ1JWUnJV
bk5UYkVWM1kwZHdWV0pZUWtkWlZsVTFUbFpXZFdGR1FsTk5SM2Q2VmtaV2ExTnJNSGhqUlZKUFVrZDRTMVZVUW5kaGJGSlhW
V3hLVGxKWGVIaFpla293WVd4YVNHRklRbFZXVmtwRVdWWlZNR1ZXVmxsV2JFWldUVVZhZDFZd1ZsTlNhekZHVGxaU1ZGWkhV
a3hWTUdNMFpERmtXRTFXVG1oaVNFSkdWbFpTVjFSWFJuRlJiVFZVVmpObk1GUnJWVEZrUmxKWVlrWkNiRlpyYnpKV1J6VjNW
akpHVjJOSVVsaFhTRUpSVlZod1VrNVdUbFpVYTNSYVRVUkdWbFpHVWs5VGJVWjFWRzVLV21FeFNrdFVWVlV4WTFacmVtRkhS
bGRTVjNkNFZsVmFUMUZ0UmxkaFJWWlRWa1ZLVUZaVVFuSk5NVTV5V1ROa1QxSlVSbmhYYWs1dlZHc3hjazVZU2xWU01qa3pW
MnBDYzJNeVVqWlRhelZzVmxkemVsVXlkRzlrYXpGSFlraFNWbUpZUW1oVmExcEdUVlpTU0dJelpHRk5SM2g2V2xWU1UxUXlS
bkpXYmtKVlRUSjRWRnBWVlRWVFJrNVpXa1UxVTAxSGREWldNblJPVFVVNVZtSkZVbFJpVlRWUFZtdGFSMDVzVGxoalNIQm9U
V3MwTWxkWWNFZFhiVXBYWTBoU1ZWZElRblZhUmxwRFZrWkdXR0pHYkZSU01rNDFWa1pvYzFOck5IZGpSV3hWVmtVMVVGWnFR
bmRrUm1SMFkwVTFUMUpZYUhoVVZXUnJVMjFLU1ZwRVVsVmlSa3B4V1ZaVk5VNVdWbkZYYXpsVFRVaENlVll4VWt0VmF6RkdU
MVpTVDFKSGVFdFZWRUozWVd4d1ZsVnNjR3hoTW5oNFdYcEtNR0ZzV2toaFNFSlZWbFpLUkZsV1ZUQmxWbFpaVm14R1ZrMUZX
bmRXTUZadlltc3hjMk5JUWxSV1IxSk1WVEJqTkdReFpGaE5WazVvWWtoQ1JsWldVbGRVVjBaeFVXMDFWRll6WjNwYVZWVXhV
bFphV0dKR1FteFdhMjh5VmtjMWQxWXlSbGRqU0ZKWVYwaENVVlZZY0ZKT1ZrNVdWR3QwV2sxRVJsWldiRkpMVTIxR2RWUnVT
bHBOVmtwMldWWlZlRkpXUm5SaVJUbE9Za1ZaZUZaVldrOVJiVVpYWVVWV1UxWkZTbEJWVkVKeVRURk9jbUZGTlU1TlJGWlpW
VEl3ZUZsWFJuSk5SRUpWVWpJNU0xZHFRbk5qTWxaRlZXczViR0pGV25kV1JFNXpWVEpXUms5VmFGUlhSMUpQVldwQ2NtVnNa
SEpVVkVKUVZsZDRSVlV5TVU5VWJGcEhVbGhvVkZZelFqWlpWRXBQVG14c05sSnNjR2xXYmtJd1ZrWm9kMkp0VWxkUmJGSlNW
ako0V2xVd1pHcGxWbEp6Vkd0d1QwMUlRa3BXUmxKUFZERlpkMk5JVWxoaVdFSlBWR3RXTkdOVk1VaGFSWEJwVTBkamQxWkhN
SGhXYlVaV1QxUldWbUp1UWxCYVYzaGhZMFprZEUxV2NHeGhlbXhGVkd0U2MxTnNSWGRqUjNCVlZteEtZVlJWVm5OalYwMTVa
RWR3YkdKVk1UUldNV040V1ZkS1ZrMVVXbUZOTVZwU1ZsUkNSMk5HWkVaVmExcE9ZWHBGTVZVeFVtdFRNVWw1WVVoR1ZWWlhV
bFJaYTFwelRteFNWVk5yY0ZOU1JVcDFWVEowYTFReVNsWk5WbFpYWWxoQ1QxVnJWa2RPVmsxM1ZHdDBXazFzV2taVk1WSlRV
MjFHV1ZwSE5WaFNSVlV4VldwR1QxWnRWa1pYYkd4T1ZrZG9NMVpFVG5OUmEzZDRVVzAxVVZaRVFuQlRNVTV5VGpCc1JVOURj
MmxMVTJzM1NVTlNRbEpyTVZaU1ZrWlFVRk5LWTAxVVVYbFlSRVV3VFZaM2VFNXFUbU5sUkZreFdFaG5lazVzZHpKT1JuZDRU
WHBrWTAxVVVUQllSRVV3VGxaNE5FNXFUbU5OVkZVeldFaG5NazVHZDNoT1JGVnBUekJDYkdSdFJuTkxRMGt2VUdsSmRVcEZS
a2RVVmxaR1ZWVTRiMGxzUWtWUFdHUm9VMFZHYmxkdE5WZGtWbXQ2Vlc1Q2FVMXFVbTVXVjNSYVRXczFObFpVVms5bGJHdzBW
REZTVTFFd01WWldhMlJNVVRGS1JWWlhkRXBOYXpVMlZsUldUMlZzYkRSVU1WSlRVVEF4VmxkWWNGSldSbXQ2Vkd4U2NrMHdO
WEZTVkZaUFVsVmFTRlJyVm1GVlJYUlpZekowVldKRmNGRlViWEJxVFZVNVZWbDZTazVXUjNOM1ZWZHdSMUl3TVhGTlZFSnFZ
bXhhYzFRemJGTlVWbFp5V1hwS1QyVnNWVEZVYm5CYVpVVTVWVlZyVGs1V1Ztd3pWVVpPUzFrd01WVlZXR3haVWtWVmQxUldX
ak5sUlRWeFZHMU9iRkpHYTNoWFJXaHVaV3MxYzJSNlNrOVNibVEwVkZod2Exa3dNVlZWVkVKWlVrVlZkMVJzV2pST1JUVnhW
RzFPVGxaR1ZYcFhSV2h1VFdzMVIyUXphRTlTUmxad1ZETnNVMVV4UlhkTlJFcFBaV3hWTVZSdWNGcGxSVGxWVld0T1RsWldh
M2hWVmxKYVRUQTFWV0Y2VGs5aGExVXhWR3RXUjFJd05VWlhiRUpVVmtSQ2NGZEZhRzVOYXpGelpVUlNUMkZyV21wYVZWSnFa
V3hvU1ZwNlNrOVdibWN3VkZod1lWa3lWa1ZVVkVKWlUwZGplRmR0ZURST1JUVnhWbTFPYkZKR2NITlhSV2h1VFdzd2VHVkVV
azlpVm5CcVdsVlNXazFHYUVsYWVrcFBWVEJyTTFOclZtRlZNVnB4VjFST1QxWkhjM3BVYlhCR1RsVTFSbE5ZYUZOaGExVTFW
MVpvUzJWV2JGbGlSMXBxVFc1b2QxZFVTbFppTVhCMFlraE9ZVlV5WkhaVmJuQmFUVEExVldGNlRrOWhhMVV4Vkd0V1IxSXdO
VVZoUlhCU1lUSjRXbFZVUm05V01IUkVZVE5DVEZVelpHNVVWVTR6V2pBMU5XRjZaRXRTVlZwRlZWZHdXazB3TlZWaGVrNVBZ
V3RWTVZSclZrcGxSa3B4VlZSc1RGTkZOSGRaTWpFMFlrZEtjRm95ZEZOaVJYQllWRzF3YWsxVk9WVlpla3BPVmtkemQxVlhj
RWRTTURGWFl6TmtXVlV5ZERCVVZrNXlZMjFOZWxWdWJHbFNNVm94VXpCT1UxSXhWbk5YVkVwUFpXeFZNVlJ1Y0ZwbFJUbFZW
V3RPVGxaV2JEUldNM0JIV2tWMFZHUkljR3RUUlhCNlYyeGpNR0l3Y0VaWGJFNVhZV3hyZWxSc1VuSk5NRFZ4VWxSV1QxSlZi
RFJWYlhCSFdXc3hjMDFJUWt4Tk1EUjNXVEl4TkdKSFNuQmFNblJUWWtWd1dGUnRjR3BOVlRsVldYcEtUbFpIYzNkVlYzQkhV
akF4VjJNemNGbFZNblI1V1hwT1UyVlhTa2hXYmxaTVVURktTRlpYZUZwTmF6VTJWbFJXVDJWc2JEUlVNVkpUVVRBeFZsZFlh
RmhsYkVwclV6Rk9NR1Z0VWtsVGJrNWhWbnBTZGxOclZtRlZNVnB4VjFST1QxWkhjM3BVYlhCR1RsVTFSbE5ZYUZOaGExcHBW
R3haZDJORmMzcFVha0pxWWxob2MxbHRiRzVoTVVwelUyeGtUMkZ0VFhoVU1WSnFUV3N4VldGNlFsSmhhMXBJVkZaYWVrMXNh
RlJoZW1Sb1ZqRnNibE13YUU5TlIwNTFVVzVhYW1WWFpISlZiWGhMVmpBMWNWbDZSbEJXUjAxNVZGWlNjazFHUm5GU2EyUk9W
bTVPTTFkR1RqTmhNVkpIVTJ0b1QyRnRUWGhVTVZKcVRXc3hWV0Y2UWxKaGExcElWRlZPYm1GV1ZrWlZWRlpyVFd0YVNsVlda
R3RVVjFaVlducHNTbUZYZERGVGExcExVa1pTVlZkVVRrOVdSM042Vkcxd1JrNVZOVVpUV0doVFlXeGFRMVJ0Y0dwTlZUbFZX
WHBLVGxaSGMzZFZWbFphVFVaS2NrOVZjRXhSTVVwRlZsZDBTazFyTlRaV1ZGWlBaV3hzTkZReFVsTlJNREZXVjFod1VsWkdh
M3BVYkZKeVRUQTFjVkpVVms5U1ZWcElWR3RXWVZWRmRGUmhlbXhSVmtSR2RGZFdaRFJsYkhCVVlrUmtTMUpVVmxSV1NIQmFU
VEExVldGNlRrOWhhMVV4Vkd0V1NtVkdTbkZUVkd4aFlsVmFlbGw2U2xaT01scFlZa2N4U2xFeWFEWmFSV2hMWkRKSmVsUlhP
VXRTVm5CVVZtMXdXazB3TlZWaGVrNVBZV3RWTVZSclZrcGxSa3B4VW0xS1RsWnFRbnBUYTFZMFZURktObGRVVGs5V1IzTjZW
RzF3Ums1Vk5VWlRXR2hUWVd0R2RsTlhkRFJPVjBsNVdrVXhWbVZyU1hkV1JWcE9aREpTUm1WR1VrNVRSa3BPVmxod1EwMUdV
a2RVV0dSclVsaG9WVlJWYUZOVVZsWTJVV3BDVlZKck1UTmFSVlkwVmtVeFNWVnJNVlpsYTBsM1ZrVmFUbVF5VWtabFJsSk9V
MFpLVGxaWWNFTk5SbEpIVkZoa2ExSllhRlZVVldoVFZGWldObEZxUWxWU2F6RXpXa1ZXTkZaRk1VbFZhekZXWld0SmQxWkZX
azVrTWxKR1pVWlNUbE5HU2s1V1dIQkRUVVpTUjFSWVpHdFNXR2hWVkZWb1UxUldWalpSYWtKVlVtc3hNMXBGVmpSV1JURkpW
VmRzVEZVeWN6VlZSbEY0WWxac1dHVkljR0ZWTW5jelUydFZNVlV4VWpaWFZFNVBWa2R6ZWxSdGNFWk9WVFZHVTFob1UyRnJh
elZYYlRGSFl6Sk5lVlpVWkcxV01uaDBVMVZPYjJWdFVrbFRibVJwVFRBeGRsTnJWbUZWTVZweFYxUk9UMVpIYzNwVWJYQkdU
bFUxUmxOWWFGTmhhMXBwVkZkM2QyTXdjRVpsUms1VFpXeHJlbFJzVW5KTk1EVnhVbFJXVDFKVmJEUlZiWEJDWWpCc2NtSkZV
bEppUlZwVlZXMTBSMkpzVm5OWmVrWm9ZbFZzTlZaWE5VTmhWMHBZVkcwMVdGWjZWbmxYYWtaVFYwZEtTVlJzY0ZkTlZXdzJW
MWR3VDFNeVRuUlVXR3hvVTBad2NWVlVRa3RWYkZWM1YydEtZVTFWY0ZsVWJHUjNZVlV4YzFOdVRscE5iWGhFV1dwS1UxTldW
blZhUjNCc1lsUnJlVlpGVWtwbFIwNUlVMnRvVTJKWVVuSlVWM0J6VGxac1ZWUnJPV2xOYTJ3MlZWWm9WMkZHWkVaaVJFcFlW
a1ZyTVZwV1duZFdSVGxZWlVkc2FWWlVWWGxXTW5SclZqSldWbUpGVWxKV01sSkxWVlJDUjJKc1RsWlVhMHBoVFVkNFJWVlda
R3BoVlhSVVlYcHNVVlpFUm5SWFZtUTBaV3h3VkdKRVpFdFNWRlpVVmtod1drMHdOVlZoZWs1UFlXdFZNVlJyVmtwbFJrcHhV
MVJzWVdKVlducFpla3BXVGpKYVdHSkhNVXBSTW1nMldrVm9TMlF5U1hwVVZ6bExVbFp3VkZadGNGcE5NRFZWWVhwT1QyRnJW
VEZVYTFaS1pVWktjVkp0U2s1TlZFSjZVMnRXTkZVeFNqWlhWRTVQVmtkemVsUnRjRVpPVlRWR1UxaG9VMkZyUm5aVFYzUTBW
a1pHV0ZwR1dsTldNbWhUVlRGV1YxWXlVbGRoTTJ4UVZqTlNhRll3Vm5OaWJGcDBUVlprYkZZd01UVlphMmhoWVZkR1ZsSnRO
VlJXVlRWRFYycENjMUpYU1hsWk1teE1WWHBTY0ZwWWJGTlNSbFp5VTFSS1QyVnNWVEZVYm5CYVpVVTVWVlZyVGs1V1ZtdzJW
VlpTV2swd05WVmhlazVQWVd0Vk1WUnJWa2RTTURWR1YyeENiVlV3YkRGVGExWTBWVEZLTmxkVVRrOVdSM042Vkcxd1JrNVZO
VVpUV0doVFlXdEdkbE5YZEhOU1JrWllXa1Z3VWsxRlduVlZNVlpQVVd4dmQySkZVbEpXTWxKTFZWUkNSMkpzVGxaVWEwcGhU
VWQ0UlZWV1pHdFRiRVYzVW0wMVZGWlZOVU5YYWtKelVrWkdXRnBGY0ZKTlJWcDFWVEZXVDFGc2IzZGlSVkpTVmpKU1MxVlVR
a2RpYkU1V1ZHdEtZVTFIZUVWVlZtUnJVMnhGZDFKdE5WUldWVFZEVjJwQ2MxSkdSbGhhUlhCU1RVVmFkVlV4Vms1a01HeHdZ
VE5DVVZaRVFUVlhiVEZIWXpKTmVWWllRbXhsVmtwUVZsZHpORTFyTlRaV1ZGWlBaV3hzTkZReFVsTlJNREZXVjFoc1VWWXhj
RzlaYTJoUFlrVTRlazFZUW1GaFZVWjJXWHBPVTJWWFRraFBXSEJNVVRGS1NGWlhlRnBOYXpVMlZsUldUMlZzYkRSVU1WSlRV
VEF4VmxkWWFGaGxiRXByVkVWT1UxUldWbkpaZWtwUFpXeFZNVlJ1Y0ZwbFJUbFZWV3RPVGxaV2JETlRNRTVMVkZaVmQxSnRO
VmRoTWxKMldUQmtUMDVXUm5SalIyeE9Za1Z3ZWxVeFZtOWhNa1pJVkdwV1VtSllRbkZaYkZwaFlqRndSbHBHWkdoTlIzaEpW
REZvVjFOc1NraFBWelZLWVZkME1WTlhjRXBrTURGeFZWaGFUbFpGYkRKVVZsSkdXakZzV1ZWWFpFNVdSa1V5Vkd4U1RtRlZl
SEJWYXpGV1lUSk5lVlJ1Y0ZaT1ZUVTJWMWhvVUZaR1NrUlVWbFphWkRCMFJGTnJjRkpOUlZwMVZURldUMUZzYjNkaVJWSlNW
akpTUzFWVVFrZGliRTVXVkd0S1lVMUhlRVZWVm1SclUyeEZkMUp0TlZSV1ZUVkRWMnBDYzFKR1JsaGFSWEJTVFVWYWRWVXhW
azlSYkc5M1lrVlNVbFl5VWt0VlZFSkhZbXhPVmxSclNtRk5SM2hGVlZaa2ExUldWbFJUV0VKTVZrUkJOVlZHWkdGaFIwcEpW
RzE0VEZkSVRuSldSM2hMVlVVMWNWbDZSbEJXUjAxNVZGWlNjazFHUm5GU2EyUk9ZV3BHZEZkV1pEUmxiSEJWWkVSc2FGWXhi
RzVUTUdoUFRVZE9kVkZ1V21wbFYyUnlWVzE0UzFZd05YRlpla1pRVmtkTmVWUldVbkpOUmtaeFVtdGtUbFp1VFhoWFJrNHpZ
VEZTUjFOcmFFOWhiVTE0VkRGU2FrMXJNVlZoZWtKU1lXdGFTRlJWVG01aFZsSkhWR3RLWVUxVlZqVlpWV1EwVjJzeGRWVnFX
bUZTYlU0elYycENjMUpHUmxoYVJYQlNUVVZhZFZVeFZrOVJiRzkzWWtWU1VsWXlVa3RWVkVKSFlteE9WbFZ1V21GbFZXeDNW
RWRzUzFKV1NrVmhSVXBPWVd4S1JGUnRjRmRSTVVweVZsUldUMUpHV2taVU1GWlhVVEE1UmxkVVVsTlNSbXN3VlZaU1JrMHhT
bkpUYTFwVFZUQnNNVk5yVmpSVk1VbzJWMVJPVDFaSGMzcFViWEJHVGxVMVJsTllhRk5oYTBaMlUxZDBjMUpHUmxoYVJYQlNU
VVZhZFZVeFZrOVJiRzkzWWtWU1VsWXlVa3RWVkVKSFlteE9WbFJyU21GTlIzaEZWVlprYTFOc1JYZFNiVFZVVmxVMVExZHFR
bk5TUmtaWVdrVndVazFGV1hkVFYyeHlZMFpDVlUxRWJHRmlWVnA2V1hwS1ZtTkhWalZWYXpsV1lYcG5lVlJ1Y0ZaT1ZUVTJW
MWhvVUZaR1NrUlVWbFphWlZaQ1dGZHRhR2xUUlRWelZIcE5lR05HY0hCUlZ6bHFUVEZLTlZrd1l6VmxhM1JFVld0a1ZtSkdh
M2xVYm5CV1RsVTFObGRZYUZCV1JrcEVWRlpXV21WR1pEWlhiVkpOVVRGS1RsWlhkR3BOYXpVMlZsUldUMlZzYkRSVU1WSlRV
VEF4VmxkWVpFeFJNSEJPVmxod1EwMUdVa2RVV0dSclVsaG9WVlJWYUZOVVZsWTJVV3BDVlZKck1UTmFSVlkwVmtVeFNWVnJN
VlpsYTBsM1ZrVmFUbVF5VWtabFJsSk9VMFpLVGxaWWNFTk5SbEpIVkZoa2ExSllhRlZVVldoVFZGWldObEZxUWxWU2F6RXpX
a1ZXTkZaRk1VbFZhekZXWld0SmQxWkZXazVrTWxKR1pVWlNUbE5HU2s1V1dIQkRUVVpTUjFSWVpHdFNXR2hWVkZWb1UxUldW
alpSYWtKVlVtc3hNMXBGVmpSV1JURkpWV3N4Vm1WclNYZFdSVnBPWkRKU1JtVkdVazVUUmtwT1ZsUkNSMlZHVWtsWmVteFJW
VEJzZDFNeFVYZFBWa0pZVjIxb2FWTkZOWE5UTVdoNllURlNjMU5zUWs5aGJVMTRWREZTYWsxck1WVmhla0pTWVd0YVNGUlhi
M2hpVm14WVpVaHdZVlpJVVRWWlZtUmFZakJ3Ums1V1RsVmxiR3Q2Vkd4U2NrMHdOWEZTVkZaUFVsVnNORlZ0Y0VwUFZrSlZU
VlJDYW1Kc1duTlRNV2d3WlZad1dWVnFSbXBpVkZKdVdrVm9TMDFXY0ZWa1JHeGhWak5vTmxkc2FEQmlSbXQ1WVVoYVMxSllh
RlJWYm5CYVRUQTFWV0Y2VGs5aGExVXhWR3RXU21WR1NuRlJWemxLWWtWS1NsWkhOV3RYYkZvMlZXMDFXbVZyTlZSVWJHUkxV
MFphVldKRmRFNWlTRUY1VjFjd01WVnRVa2RqU0ZKVFlteEtiMVpxVG01TlZsRjVUbFpPYUZJd1drbFVNV2hUVjJ4YVNWVnVV
bHBoYTJ0NFZGVldORmRXVW5WUmJYaHBWbXhWZVZaR1dsTlRNbEY1Vm10V2ExSXpRbkJVVnpWdlRXeHJlV05JV21oaVNFSXdW
akZTVTFSdFZuSlNhazVWWld0d2FGcEhNVXRrVmxaWlZXMTBUbUpHY0ROV01uQkxZakF4Ums5WVVsUmliSEJ3VldwR1JrMHhU
blZpZWtaVFRWZDRXbFpYTVc5aFZrVjNVMnRrV2sxcVZreGFSekZQWTBaR1ZWZHNSbEpsYlhjeVYxUkNhMUl5VWxkUmJrSlNW
a2RvY1ZSVVFrdGlNV3gwWWtWT2JHSldTa3BaYTJoUFdWWmFSVkZ1VmxoaVZFRXhXa1prVTFKRk1WaE5WbkJYWld0YU0xZFhk
RzlqYXpWMFZXdG9VMkpVYkhCVVYzQkhZakZTTmxOdFJtdGlWWEF4Vmxab1UyRnJNWFJrZWtwWVlrWktNbHBWVlhoa1ZrWnhW
V3hDVG1GNlZYbFhWM1JxVGxkV1ZrOVlRbFZYUjFKUFZXdFdSMDB4VWxaVmEwcFBUVWhCTWxSc1dsZGhSa2w0Vm0wMVdHSkhU
WGhaVnpGS1pWWldkR1ZIUmxKTlJYQXdWMVphYTA1SFNrWmlSVlpyVFRGd2NWUlVRa3RpTVd4MFlucENVRkl3TVRaVlZ6RnZZ
VmRHVmxOcVdtRlNWMmg2V1hwR2QxWlZNVWhPVjBacFZrZDNlRmRyVms1bFIwcFhZa1pvVGxkRlNuQlZNR1I2VFd4d1JscEZa
R2xOYTJ3MVZGWmtiMVZGTVhOalJFcGFZbFJXVTFwRlpFNWxWMHBGVjIxR1YxSjZhekJXUm1NeFVUQTFSazlJYkZWaWJIQndW
VzV3YzA1V1VYbGlSVGxvWWtkM01sVldhR3RVYkVwR1VsUk9WR0p0T0hkVlJrNUtZMFY0Y0ZGcmFFOWhiVTE0VkRGU2FrMXJN
VlZoZWtKU1ZsWnJkMVF3Vm5OUk1VNVhZVVZTV0ZKc2JIWlRNVTVDWkZWd1JtVkdUbE5sYkd0NlZHeFNjazB3TlhGU1ZGWlBV
bFZzTkZWdGNFSmlNR3h6VVd0U1VGZElRbkZWYWtKaFRWWldTR05FVW14aVZUVkpWVzAxVjFOc1RrWk9TR1JoVm0xUk1GbHJX
a05XUm5CSVRWZHNUbUZzVmpOV1JWcHJXVmRHU0ZOc2FHbFRSVFZ6Vm10b1FtUXhaRmRhUnpscllsVndXVlZzVW10WlYwcFZZ
a1JHWVZKVk1UUmFWekZIVjFkT1NHVkdRbWhoTVZreFYxUkNiMkpyTkhoaE0yeFFWMFUxY0ZSVVFuSk5iRTVaWTBWS2EwMUVS
a1pWVm1oclZHeEtTVlJ1VmxaU01uaEVXVEJrVDA1V1JuUmpSMnhPVFVoQk1WZHJXbTlSTURGR1pVaEdhMDB4Y0hGVVZFSkxZ
akZzZEdKNlFsRlZNR3gzVkhwS1YwNUhSbGxWVkdSdFYwUkdWRlZ0Y0ZwTk1EVlZZWHBPVDJGclZURlVhMVpLWlVaS1ZsZFhP
VXBoYkZZeFZGVk9TbU5GT1RWUlV6bFJZVzVqZGxrd1pHOWtNR3hFVld4Q1ZsSlVWbEpWYWtaVFZqRkdjbFJVYkVwaVNHUTBW
R3RTUzFrd01WVlZXR2haVWtWVmVWUlVSalJPUlRWeFZtMU9iRkpGTUhsWFJWSmFUVVpvUlZKWWNFOU5XR1EwVkd0U1Uxa3dN
VlZWVkVaWlUwZGplVlJVUmpObFJUVlZXa2RPYkZKR2EzZFhSVkpHVFVVMVZGTlVaRkpTTVZsNVYxWmtNMkl3Y0VaUFZrWlZZ
a1ZLU1ZacldtRlJNVVkxV2pKc1ZHRXhXbEJWTW5oaFZteFdjazFXWkU1V00yaFhWbXhTUTJReFRYaFdXR1JXWWtoQ1YxVnNX
bUZTVmxaeVYyNU9WbFpzYkRSWGExcFhWV3N4Vmxkck1WWldNMUpRVmtWYVQxWnRTa1pUYkZKWFZteFZNVlV4WkRCa01VNVlV
MnRvYUZKdVFsaFZiR1F3VGxaYVJWSnNUbXBoZWtJMFZWZDRVMVpIU2xaT1dFNVdZVEZ3ZVZwVldrOVhSMDVKWTBkb1RsZEZT
bGRXVnpCM1pVWlJlVkp1VWxwTk1taFdWRlphUzFZeFVYaFdiazVxVFd4YVJsWlhjelZpUjBZMlVtNWtWMUpGTlhwV1ZFcEhZ
MnN4Vmxac1VsZFNiSEJRVmtaa01GWXdOVmRhUm1SV1YwZFNiMVp0TVc5U2JGWnpWV3QwVldKV1dsZFpXSEJQVmtkRmVXRklT
bHBXTTAxNFZHMTRjMDVzVW5SU2JFNVRZbGhPTTFac1ZtRlVNVTE0WVROc1YyRXhXbUZVVkU1RFV6RnNXRTVWT1dwaVIzUXpW
bGMxVDJGR1dsVlNWRXBYVW14S1VGWlZXbUZTYlVZMlZteFNWMDB5WkRaWGJGWldUVmRPVjFSc2FHbFNhMHB6Vm0xMGQyVldX
a2RhU0dSVVlYcEdSMVJzVW1GVk1rcFlaVWRHV2xZelFrUlpNRnByVW0xR1NFNVZOV2hsYkZvelZsUkdVMkV4VFhoVWEyaFZW
MGhDVmxadGVFdGpiR3hWVW01a1ZGSnJOVlpXYlhoSFZtc3hTVkZ1UmxkU2JIQlVWakl4VW1ReVNrbFNiWEJPWW14S2VWWlhk
R3RWTWs1eldraFNhbEpXY0U5VVZWSkRUbFpaZUZWdVRsUmlWWEJLVjI1d1ExWlhTbGxSYmtaYVZteFZNVlJzV2xkalZsWnpZ
MFUxYVZKWVFqUldha2w0WkRKRmVWSllhR3BTYkZwUFZXcEtORmRzVm5KV2JGcHJUVlpLV1ZsclZuZFVNREZKVVd0d1ZsWnNT
bGhXTWpGSFkyc3hWVmRzVm1saWEwbzFWMnhhVjA1SFRsWk5WV2hwVW0xNFZGVnNXbGROTVZwSVpVVTVWR0Y2VWpOWk1GWnpZ
VVpKZWxGdGFGWmhhMFY0V2tWYVUyTldUbk5VYld4VFYwZG9ORlpHVm10ak1XUlhWR3RvV2sweWVGWldiWE14VXpGU2NWRnVa
RlJXYkZwV1ZrY3hSMVV3TVZoa2VrSlhVbnBXTTFaRVNrdFdiVlpKVW1zMVUwMXVhR0ZXVkVKcllXMVdjMXBHWkZWaE0wSlFW
RlZvUTFOV1drZGhSM1JWWWxWYU1GcEZVbUZYUmxwelUydDRXbFpXV1RCWk1WVjRVMGRLUjJKR1VsTk5helF5Vm10a01GUXhU
bkpPU0dScVVsZDRjRlZxU205WlZscHlWbTVLYkZac1JqUldWelZMVkdzeFNXRklhRlZXVjFKNlZqRmFUMU5HYTNwalJsWk9V
bFJXVEZaSGNFTmtNVTVYWTBWb2FsSlViSE5aYkZWNFRteFplV1JHVGxoaGVsWkhXbFZvUjFSc1NYbGxSbkJXVFVkU2NWcFdX
bEprTVZKeVkwVTVUbE5GU2t0V01XaDNWREZTYzFkcmFGWmlhM0JaV1ZSS1UxTXhVbk5XYWxKcVZsUkdTVmxyV25OV1JrbDVW
R3BhVjFKdFVucFZNbk13WkRKT1JtRkdaRmhTTW1odlYxWmtOR05yTVVkVmJrNVdZbXR3VDFac2FHOWxSbEpXV2toT1dGWnJO
VWxYVkU1M1ZsZEZkMDVWZEZWaGEwb3pWVEJhVjFkWFNrWk9WMmhwVWxaWk1sWnJXbUZoTVUxNVVteGFiRk5GV2s5VmFrbzBW
MnhWZDFwSGNHeFdiRVkwVmtkMGQxUXdNVWxSYTNCV1ZteEtXRll5TVVkamF6RlZWMnhXYVdKclNqVlhiRnBYVGtkT1ZrMVZh
R2xTYlhoVVZXeGFWMDVXV2tkaFNFNVVZWHBTTTFrd1ZuTmhSa2w2VVcxb1ZtRnJSWGhhUlZwVFkxWk9jMVJ0YkZOWFIyZzBW
a1pXYTJNeFpGZFVhMmhhVFROQ1ZsWXdaRFJUTVZKeFVXNWtWRlp0ZHpKV1IzTXhWakZKZUZOdWJGZE5ibEp5VlRKek1WWnRW
a2xTYXpWVFRXNW9ZVlpVUW10aGJWWlhWVzVPVldFelFsQlVWV2hEVTFaYVIyRkhkRlZpVlZvd1drVlNZVmRHV25OVGEzaGFW
bFpaTUZreFZYaFRSMHBIWWtaT1UxWnNXVEZXYTFwcll6Rk9jazVJWkdwU1YzaFhWbXRWTVdGR1ZuRlNiazVQVW14S01Wa3dW
VFZoVmxwWllVaG9WVlpYVW5wV01WcExVbXhhV1dGR1ZsZGhlbFpNVmtkd1EyUXhUa2RYYkd4b1VqQmFiMWxyWkhwa01WcEla
VVpPYW1GNlJsZGFWV2hIVkd4SmVXVkdjRlpoTVZwWFdsWmFVMWRIU2taalJUbE9VMFZLUzFkWGRHdGlNV1J6VjJ0YWFFMHll
RlpVVmxwTFpWWlNWVkZxVW1wV1ZFWkpXV3RhYzFVeFNsZFdhbFpYVW14YVVGVXljekJrTWs1R1lrZEdVMDB3U2xGWGJHTjRW
RzFXYzFwSVNsWmlTRUpRVld4b2IyVkdVbFphU0U1WVZtczFTVmRVVG5OV1ZscHlUbFYwVldGclNqTlZNRnBYVjFkS1JrNVhh
R2xTVmxreVZtdGFZV0V4VFhsU2JGcHNVMFZhVDFWcVNqUlhiRlYzV2tkd2JGWnRVbHBaYTFaM1ZEQXhTVkZyY0ZaV2JFcFlW
akl4UjJOck1WVlhiRlpwWW10S05WZHNXbGRPUjA1V1RWVm9hVkp0ZUZWVmFrcFBUbFphU0dWRk9WUmhlbEl6V1RCV2MyRkdT
WHBSYldoV1lXdEZlRnBGV2xOalZrNXpWRzFzVTFkSGFEUldSbFpyWXpGa1YxTnJXbGRYUjFKV1dXdFZNVk14VW5GUmJtUlVV
bXhhV2xsclZURmhSMHBHVm1wYVYxSnNjSEpWZWtwSFZtMVdTVkpyTlZOTmJtaGhWbFJDYTJGdFZsZFZiazVWWVROQ1VGUlZh
RU5UYkd4eVdrYzVXR0pHY0VkWlZFNXpWbFV4VjFOdWJGVldSVWt3V1RGVmVGTkhTa2RpUms1VFZteFpNRlpyWkRCVU1VNXlU
a2hrYWxKWGVHaFZhazVEVlVad1dFMVZOV3hpUjFKNVZsZDRkMVF4U2xsaFNHaFZWbGRTZWxZeFdrdFNNa3BGVld4U1YwMHdT
VEpYVmxKSFpERmFWMU51VWs1V1ZHeHZXV3hrVDA1R1drVlNiRTVYWWxaS1dGVlhlRk5XUjBwV1RsVTVWMkV4VmpOYVYzaFBW
MGRPU1dOSGFFNVdNMk41VmxaYWIyTXhWa2RYYTFwUFZucHNWbFp1Y0VkU01YQkhWMnRPVjFKck5UQldSM014VmpGS2NsZFVT
bFpOVjFJeldsY3hSbVZXVm5GWGJIQk9UVzFvVVZaR1pEUlRNazV6V2tab2FsTkZjSEpXYlhSTFZsWlZlVTFWT1dsU2EzQklX
VlJPZDFaR1dYcFJhbHBhVmtWd1ZGVnNXbmRUUjFKSVVtMW9hRTFXV1RKV2ExcFRVekZrZEZWc1pGSmlSa3BWV1d4Vk1XTXhW
bkpYYm1ScVRWVTFXVmt3YUhkaFJURlpVV3hhVm1KVVFqUldSRXBIWkVacmVtRkdWazVXYTNCWVYydGFZVkV4V2tkVWJsSlZZ
bGhvVkZWcVFUQk5WbFY0V2toa2JHSlZWalZXYlhSdlZsZEtXVlZ0UmxWV00yaDVXbGQ0YTJOc2NFZFhiWFJYWVRCd1ZsWlVT
WGhVTVZGNVVtNU9hVk5GU2xaVVZFbzBWa1pTVmxaVVFteFdiRm93VkZaVk1WWXhTWGRPUkVwWFRXcEdlVlJWVlRWV2JVcEpW
RzEwVGsxdGFGRldWM1JoWXpKT1IxVlljR2xTYXpWUFZGVlNWazFzVm5OVmF6bGFWbXRzTkZVeU5VTldWVEZXVFVoa1ZVMVdX
bnBaZWtwWFVsWldjazlXWkU1V00yZ3pWbXRTVDJNeFZYbFZXR1JRVm0xb1ZWWXdhRU5VTVhCWVRWVTFUbFpzU2xsWldIQkRW
VVphV0dWSWNHRldWa3BVVmtSS1YyTXhTblZSYkZaT1RXNW9WVmRXWkhwbFJrcFhWR3hXVjJGNlZrOVpWRVphVFZaWmVGZHJO
VTVTVkd4WFdXdG9UMkpHU2tkVGJFSmFZa1p3U0Zrd1dsSmxiVXBIVkdzNVYySllhRnBXVjNodll6RlJlVkp1VW1wbGExcFdX
VzB4ZW1ReFVYaFdiazVxVFd4YVJsWlhjelZoVmxwMFpVaGtWMUpGTlhwV1ZFcEdaREF4V1ZKc1VsZFNWWEJSVjJ4a01GbFhU
a2RhUm1ob1pXdEtVVlpzWkRSbGJIQkZWRzA1VldKV1dqQldSelZEVmxVd2VXVkdVbHBoTVZZMFZqQmFhMVpXVG5OUmJFNVRZ
a1pXTkZaclpEUlVhekZHVDFaYWFWTkZOWE5WYTFwTFZVWnNjMWR1VG1wU2JFWTJXV3RhVDFSck1VVldWRXBXWWtaS1VGZFdX
a3BsUms1MVVXeFdUbFpVUWpOV1JFWlhZekpOZVZaclZsWmhlbXh6V1ZSS05HUXhXa1ZVYms1V1RVUldTRmxVVG5OV2JVcFpV
VzFvV2xaNlJsUlpNbmhyWXpGU1ZWRnNRbGRXTTJnMlYydG9kMU14VVhoU1dHUm9aV3MxVlZsVVNtOWxiR3hXVjI1T1YxWnJO
VlpWTW5oRFZqRkplbFJxV2xkU1YxSXlXa1JLUjFZeVJrWldiRTVYVWxWd1VWWldVa05qYXpCNFZHNUtZVkpyY0hOV2JGSkha
VVphVjFWck9WVmlSbXd6V1RCb1ExWldTWGxQVkU1YVZteHdkbFV4V25kT2JFNXlUMWQ0VjFZelRqWldWRW93WVRGU2RGVlla
RTVYUlZwdlZGUk9RMWxXV25KV2JtUnBUVlpLU1ZscVRtdGhSbHAwWVVWd1dHSnVRbEJXUjNoR1pESkdObFJzVW1oTmJXaFVW
a1pTUjJReFRrZGFSbXhvVWxSc2MxbFljRmROUmxwR1draGFhMDFyV2xoWlZWWlhWVEZhUmxkc1FsWldSVXA1VkZSR1QyTnNj
RWRYYlhoVFltdEtORlpxUmxOVk1WRjRWMnRvYWsweWFGWlZha2sxVFRGc1YxZHRPVlJXYmtFeVZrZHpOVlZyTVVoak0zQldU
VzVTZGxaWGMzZGxSMDVHWVVab1dGSXlhRkZYYkdSNlRWZFNSMVZyYUdsTk1sSnZWbTB4YjFOc1pIVmpSWFJWWWtaV00xbFlj
RTlXUjBWNVlVaGFXbFl6YUROVk1GcFRaRVV4Vms5Vk5XbFNXRUkyVm10V2EyUnRVWGxTYkdSb1VsWmFWRll3YUVOVlJuQlhW
bTVLVGsxVk5YbFpWV1IzVkdzeFJWSnNXbGhoTWxGM1YxWmFTbVF5UmpaU2JGWlRUV3ByZWxkV1dtRmlNVXBYVTJ4V1VtRjZi
RmRVVldSNlpXeFZlV1JIZEZaaVZscFhWRlpvUjFac1drWk9WVGxYWVd0S00xa3dXbE5UUjBsNldrZG9WMkpYYUVkV1YzaFRV
VEZSZUZaWVpHaGxhelZWV1ZSS2IyVnNiRlpYYms1WFZtczFWbFV5ZUVOV01VbDZWR3BhVjFKWFVqSmFSRXBIVmpKR1JtSkhS
bXhoTTBKUlZteFNRMk5yTUhoVWJrcGhVbXR3YzFac1VrZGxSbHBYVldzNVZXSkdiRE5aTUdoRFZsWkplVTlVVGxwV2JIQjJW
V3hhUzFaV1RuSlBWa3BPVWpOT05sWlVTakJpTWtaeVRVaGtUbFpzV21oVmFrNVRZVVpXY1ZGdVNrNVdiRXBKV1dwT2EyRkdX
blJoUlZwV1lrWktURlpIZUVaa01rWTJWR3hTYUUxc1NsaFhiRlpoWkRKT1YxcEdWbFpoZW14WVZXcE9UMDVHV2taYVNGcHJU
V3RhV0ZsVlZuTlpWVEI2VVdzNVYxWkZTbmxVVkVaUFkyMUdSMU5yTlU1WFJVcEtWbXBHYjJJeFVYbFdia3BQVjBaS1ZsVnFT
VFZOTVd4WFYyMDVWRlp1UVRKV1IzTTFWV3N4U0dNemNGWk5ibEoyV1dwS1IyTXlUa1poUmxacFZrVmFVVlpHWkRSVE1rMTRW
V3RvYVUweVVtOVdiVEZ2VTJ4a2RXTkZkRlZOVlZZeldWaHdUMVpIUlhsaFNFWlZWa1ZHTkZac1duZFRSMUpJVW14T1RsSXpU
alJXYTFaclpHMVJlVkpzWkdoU1YzaG9XbGQwZDFsV2NGZFdia3BPVFZVMWVWbFZXa3RVYXpGWlVXdHNWMDFYYUhaV1JFcFhZ
MnMxV1ZWc1ZsTk5hbXQ2VjFaYVlXSXhTbGRUYkZaU1lYcHNWMVJWWkhwbGJGVjVaRWM1YVUxclducFpNRlp2VmxkS1ZWSnNR
bFZXTTJoTVdYcEdVMU5IU1hwYVIyaFhZbGRvUzFZeU5YZFRNVkY0Vmxoa2FHVnJOVlZaVkVwVFZqRndWbGR1WkdwV2JWSldW
bGR6TlZZeFNYcFVhbEpYVWxkU01scEVTa2RXTWtaR1lrZEdUazB3U2xCWGJGcFhZMnN3ZUZSdVNtRlNhM0J6Vm14U1IyVkdX
bGRWYXpsVllrWnNNMWt3YUVOV1ZrbDVUMVJPV2xac2NIWlZiRnBMVjFkT1JrOVhlRmRXTTA0MlZsUktNR0l5Um5KTlNHUk9W
bXhhYUZWcVRsTmhSbFp4VVc1S1RsWnNTa2xaYWs1cllVWmFkR0ZGV2xaaVJrcEVWa2Q0Vm1ReVJqWlViRkpvVFd4S1ZGZHNX
bFpsUjA1WFZteFdWV0Y2VmxoVVZXaERaREZhUmxwSVdtdE5hMXBZV1ZWV1YxVXhXa1pUYXpsaFZteGFlVlJVUms5amJVWklU
MWRvYVZORlNrcFdhMk4zWlVaUmQwMVlUbFJpYXpWV1ZXcEpOVTB4YkZkWGJUbFVWbTVCTWxVeWN6RmlSbHBZWXpOd1ZrMXVV
bkpXVnpGWFZtc3hXVkpzV2xkU1ZGWlFWbTF3UTJReVRrZFZhMmhwVFRKU2IxWnRNVzlTYkZaelZXdGtWVTFzV2xkWldIQlBW
a2RGZVdGSVdscFdNMmd6VlRCYVUyUkZNVlpQVlRWcFVsaENObFpyVm10a2JWRjVVbXhrYUZKWGVHaGFWM1IzWVVac1YxWnVT
azVOVlRWNVdWVmtkMVJyTVVWU2JGcFlZVEpSZDFkV1drcGtNa1kyVW14V1UwMXFhM3BYVmxwaFlqRktWMU5zVmxKaGVsWnpW
bXhrZW1Wc1ZYbGtSM1JXWWxaYVYxUldhRWRXYkZwR1RsVTVWMkZyU2pOWk1GcFRVMGRKZWxwSGFGZGlWMmhIVmxkNFUxSXhV
WGxTYkZwb1pXczFWVmxVU205a2JHeFlaVWhrVkZKc1NqQlVWbFUxVkcxS1JtTkljRmRTVjFJeVdrUktSMVl5UmtaV2JFNVhV
bFZ3VVZaV1VrTmphekI0Vkc1S2FGSXpRazlVVlZKSFZteGtjbHBFUWxwV2Eyd3pXVmh3UjFaV1NYbFBWRTVhVm14d2RsVnNX
a3RXVmtaeVQxWmFUbEl6VGpaV1ZFb3dZVEZXZEZac1drNVhSVnBYVm10Vk1WUXhXbkZSYm1ScVVteEtTVmxxVG10aFJscDBZ
VVZhVm1KR1NreFdSRVpHWkRKR05sUnNVbWhOYldneVYxWmtOR1F4VGtkVmJsSk9WbFJzVDFsclZuZGxiRnBHV2toYWEwMXJX
bGhaVlZaeldWZFdjbU5HUWxkV1JVcDVWRlJHVDJOc2NFZFRiWGhYVmtWYU5GWnNXbE5VTVZKelYxaGthbE5GU2xaVmFrazFU
VEZzVjFkdE9WTldhM0JXVlZkek5WWnJNVWhqTTNCV1RXNVNjbFpYTVZkV2F6RlpVbXhhVjFKVVZsQldiWEJEWkRKT1IxVnJh
R2xOTWxKdlZtMHhiMUpzVm5OVmEyUlZUV3RhVjFsWWNFOVdSMFY1Vld4T1lWWnNWalJaTVZwWFZsWlNjazVXWkU1VFJVb3pW
bXRXYTJSdFVYbFNiR1JvVWxkNGFGcFhjekZWUmxaVlVXNUtUazFWTlhsWGExcExZa1phVlZKdWFGZFdiRXBRVmtkNFdtUXlU
a2xSYkZaVFRXcHJlbGRXV21GaU1VNVhZMFJhVldGNlZuTldiR1I2Wld4VmVXUkhkRlppVmxwSFZERldWMVl4V2taalJUbGFW
ak5vYUZwRldsZFNNVkp6VkdzNVYySllhRnBXVjNodll6RlJlVkp1VW1wbGExcFdXVzB4TkdSV2JGZFhiVGxUVm14d1dsZHJW
bmRoUm1SSVlVaFNWMDFHU2tSWFZscFBZekZXY2xkc1pHbFdia0poVjJ4YWEyVnJNSGRpU0VacVRXNVNXVlZzVWtkU01WbDVZ
M3BXVTFack5VZFZNbmhMVmxVeFJtTkZUbGRTUlZwTVZYcEdZVkpzVm5KV2JHaFhZbFpLYUZaV1dtRlJNVlp6VjI1S1ZXRjZW
bGxWYTFKRFkwWmtSbFZyV2s1U1ZFWjZXa1JPYjFReFNrWlhiWEJWVm14S1lWcFhlRzlUVm5BMlUyczVWMkp0WTNkV1JtaDNX
VlpyZDA1WVJsWmlWVFZQVm10VmQyVnNaRVpWYTFwT1VsUldTRnBFVG05VU1VcEhWMjF3WVZaV1NtRmFWM2h2VWxaS1ZWSnJP
VTVYUjJOM1ZrY3hkMVV4YTNkTlZsWldWa1ZhUzFsWE5VOWpiRnB4VW14T1ZVMVhVbGhWVjNSelZrWmFWMU50YUZaV1JWcE1W
VEo0VjFJeFRsVmlSWEJwVTBkamQxWkhNWGRoTVd0NVZtdFdZVkpWY0ZwVk1HUnFaVlpSZUZkcVVrOVNWRlo1Vm0weFQySkdT
a2RoTTJ4WVVsZG9kVlJYZUVabFIxWkZWV3M1YUdFeGNIRlhiRlpUWVdzeFIyRkZiR0ZsYTNCUVZsUkNjazB4VmxaYVJtUk9Z
a2Q0V1ZwRVNUVlRiVVp4V2pOS1ZGWXljM2RaVkVaYVpVWldjazlXYUZkaE1IQkxWbFJHVjFVeGJGZFNXR2hVWVROQ1YxVnRk
SE5rYkZKSllraGFZVTFJUWtoWmExWXdWa1phVm1ORmRGZFNiSEJJVldwR1QxSXhjRFppUlhCcFZsZDNNVlpHV210VWJWRjNU
VlpXWVZJemFFOVpWM04zWld4a2RHTkdTazlXYmtKSlZrWm9kMVJzV2tkWGJrWlZWbTFTYUZsVVJuTmtSa3BWVTJ4d2FHSldT
bmxXUmxaVFZHc3hWbUpJUm1wTmJWSk5XVlpTYjJOc1dYZFdXR2hUWWtkU2VWUXhWbGRXUmtwV1kwWk9WMUpGU2t4VlYzaFNa
VVpPY21SR1pGTldhM0JIVm1wR1lXRXhaSE5TYkdSU1lrVndXRmxVUVRGVU1WbDNWRzAxYUZZeFNucFdNalZQVkRGWmQyRjZS
bUZTVmxwNldsY3hVMk5YUlhwWGJVWk9WbFZ3TlZaclVrTmhNbEpHVFZWa1lWSXlhSEpVVlZKV1RVWndWbFp0Um14aVJUVkpW
VEo0UzFsV1duUlBTR3hYVWxad1VGcEhjM2hrVjA1SllrWm9VMVpXVmpOV2ExcGhZVEZhUjFwR2FHdFNiSEJVVld0YVlVMUdi
RlZTYkU1c1lrWmFTVll5TUhoWGJVcHpWMnBTV21KSGFETmFSRXBPWlVkT1NFMVdUbE5OVm13MlZUTndRMk15VFhsU1dHeFZZ
a1pLYjFsWGVITk5iRkp6VjIxR2FFMVhVa2haVldoUFlrWktSMk5GVWxoV00wSkRXbGR6TVU1Vk9VVlViR2hvVmtkME0xWnFS
bTlaVjFKSFUycFdVRkpGV21GV2EyUXdUV3hrV0dOSFJtaGlSa3BHVlcxNFEySkhSWGxsUlU1VllrWndVRlpXV25ka1JscDFW
bXQ0VGxKc2NGSldSbHB2VmpGV1NGVlliR2xTVkVaVlZUQmtOR0ZHYTNsT1ZUbFZWbXhhVmxscldrTldWMHBWVmxod1lWTklR
bkpVVjNoWFYwZFdSazlWTlU1aWJWRXlWMWMxZDJJd01YSmtNM0JTWVRGS1VGUlVRbFpsUmxwMFpVYzVWbFl4V2twWmFrNUxW
bXhLY2xOclVsZGlSMUpvVkZaYVlXUkhUa1ZSYkU1WVVtdHZkMVpyWkhwTlJURllVbGhvVW1KdGFITldhMlJ2VFZaWmQxZHJU
bXROVlZreVdXdFZlR0ZYVm5SVmExcFhWbGRvUjFSc1dsSmtNRGxaVm1zNVZGSlVWazlYYTFacVRWWlplVk5yWkZSV1JWcFVW
bTAxUTFkc1VsbGpSVnBPVm0xNE1WVXlNVEJVTWtwWVQxUktWVTFXVlRGV1ZsWXpaREZ3Um1WR1drNVNhMVkyVjJ4YVYxWXdO
VlpQVlZwcVVteGFVRlJVU2xOT2JGcFdWV3R3YkZac2NIZFVNVlUxVmxaYVJtTkZNV0ZXTTBKUVZHdGFhMWRXUm5OalJrSlRU
V3MxTlZZeU1IaE5SbVJJVld4b1ZHRXpVbFJVVnpFMFRWWlJlRlZyV210aVIxSkdWV3hvYzJKR1NYaGpSVFZZWVRKb1QxcFdW
VEJrTWtZMlZHMW9UbEpzY0UxV1ZFSmhVakZOZVZKWVpGcE5NbEpZV1d4YVMwMXNVa1ZUYWtKVFVqQTFlVnBWYUVkWFIwcElX
bnBHV0dKRk1ERlpNakZUWTFkS1JrNVhSbXhoZWtGM1YxZDRWMU15U1hsV2JHUlZZbTFvVVZadGRHRlhWbXh6VjIxR1ZWSnVR
bGxXUnpWTFZqQXdlRmRzVmxkTlYyaElWR3hhYTFZeFRuUmxSbVJwVjBVeE5GWkhkR3BsUjAxNFZteGFXR0pyTlZsWmJHUlRV
VEZXTmxOdVpFNU5iRXA2VmpGb1lWVXhaRWxSYlVaaFZsZFNWRmxVU2twbFJtUjBVbTFHYUUxV1dYbFdNV1EwWWpKS1ZrOVdi
R3BTTTFKWVZGZDBkMDFzWkZoTlNHaFhZa1phTUZaV2FHdFVNREZIVTI1b1YwMXVRblZaTW5oVFZtMUtTVlpyTlZkbGEwa3lW
MVpXVjFkck1WWk5TR1JZWVRGd2FGWnJaRzlVUmxwelZXeE9hRkl3V2pCVVZsWjNZa2RLV0ZWcVZscFdSVnBZVmpKek1WWlhS
a2hsUmxaT1lXMTRXVmRZY0VOa01sWklVMWhvVm1KdFVsbFdha0V4Vkd4V2RHVkZXbXBpUm13elZrZHdSMWRHV1hkWGJsSlhU
VWRTVkZWNlJrOWpiVXBGVW14S1RsSldjRXhXVkVKWFVqRktkRkpZYUdGU2EwcFpWbXhvUTFFeFVuTldiWFJXVFZaYVdsWldV
a05XUjBwelVtNXNWMUpGTlV4Vk1qRkdaVlpXZEU5V1VsTk5NazQwVjJ4YVUyRXhWWGhUYmxKVFlUTkNiMWxVU205alZscEhW
V3hrVjFKck5WbFdiWGhIVjBkS1ZtTkdaRlZOVjFKVVdWVmFkMU5IUmtWVWJHUnBZVEowTkZadGVGTmpNazVYVldwV1VGWlZO
VmhWYkZKR1RXeGFTR0pFUW10TmExcEdWbGR3VTFac1drZFNWRVpXWWtkUk1GUnRNVmRUUjA1R1ZtMXdhVmRIVWpWV1ZtaDNZ
bTFXUms1VldsaGlWRVp4VmpCV1MySXhXbFpYYkdSWFVqRmFTVlp0ZEd0V01rcHpVMnRPWVZaWGFGUldWbFV3WlZaS2NWSnJO
Vk5OTURCM1ZtcEdWazFXV2toVmJrWnBVbGRTV0ZsclpEUlNSbHB4VW14a1ZFMXNTVEZVTVdONFZqSkZlVlZZYUZwaE1sSklX
a1JLVG1ReVRrZFhiWGhYWWtoQ1dsZHJVa2RaVjBaelYyNVNZVkpWY0hGWldIQlRUVEZzY21GSVRsVlNNSEJZVm0xMGQxWkdT
bGxSYTNCYVRXNW9TRlp0ZUVabFYwNUhVV3hDVkZKdVFucFhWM2hoVWpKV1JrMVZiRlJoZW14dlZXMTRTMlJXV2xaV2JFNVdW
bXRXTmxsVlZqUlViRXBYVjJ4Q1ZVMUdjRkJVYTFwclZqRmtXVnBIYkZkTmJFcFFWakZTVDFack5WZGlTRlpVVmtWd1dGbFVT
alJXUmxsNFZsaG9UbFl3YkROVk1uUTBWMGRLVlZvemFHRlNNMmg2VmtaVk1HVnNVbkppUjBaWFZsUldWbGRyVWtkak1ERnlU
bGhHVjJKcldsWldibkJ6VWpGYVNHVkZaRTlTYkhCNVZrZDRkMkZWTVZaWGJIQllWa1ZyTVZscVNsTlNiRnAwVGxaV1RtSllh
RWxYVkVwM1ZqSldXRlZzVmxKaGEzQlVWbXBLTkdNeFVYZGFSMFpYVm10d2VWUlZhRzlVTVU1SlVXeENWbUV4U25KWmJYTXhW
MVp3UjFac1VrNVNlbXQ1Vmxkd1MxbFhTbk5XYkZwc1VucFdUbFJWVm1GVE1XUlZWR3RrYW1KVlduZFVNV014VjJ4a1JsTnFX
bHBoTVVwMldWZHplRkpzVW5WUmJHUlhUVEJLTkZac1VrOVRNVTVZVm01T1ZXSllVbk5WYlRGVFZWWnNWVk50UmxaV2ExcFlX
V3RrZDFVd01IbGtla3BZWVd0YVZGWXhWak5rTVZweFZtMTRVMUp1UWxWWGJGcHFaVVpTYzFkcldsTldSbHBvVkZWU2MxSldh
M2hXYWxKVlZqQndWMVZ0ZUdGV1IxWlpWV3hHV0dFeFNtaFdha1poWTIxU1JWUnNVbWxXTW1oNVZtMTBVMVJ0VG5KUFZsWm9U
VEZ3Y2xaclpHdE5NV3Q2WTBkd2EwMUlaRFpWYlhoWFlVWkplVTlZYkZwTmJsSllWRzE0WVdOV1RuTmpSa0pYVWxad2VGWnRN
WGRoYXpGSVZHeGFUMVl6YUU1YVZsSnpaVlphZEUxWVpHbE5iRVl6VlRKNFlXSkdTa2RYYWtwVlRVZE9ORnBFUVhka01YQkpZ
a2RzVkZORlNsZFdiRkpMWkcxV1YxZHFXbEpYUjNob1ZsUk9VMlJXYTNoV2F6bHBUVmhDU1ZsVlVsTlVNREZaVVc1b1ZrMUhV
ak5aVlZwTFYwZFNTRTlXVmxOWFIyTjNWakp3UzFNeFdsWk9XRlpZWW1zMWNGWnJXbmRrVmxaMVkwZEdhRkpyV2pCVmJYaEhW
REF4UmxaWWNHRldiSEJNV1RCYVUyTnRTa2xpUm1ocFlsUnJlVmRzV2xkak1sWnlUMVphVDFOR2NGbFdiWGhoVFVaYWNtRkZP
VlJOVmxZelZUSjRhMVJ0U2xsUmEyUlhZVEpTV0ZkV1drdFhSVEZZWkVad1YwMXRaekZYYTFKRFZUSlNWMkpJUm1oU2JGcFdW
V3BLVTAweFdYZFVibkJQVmpCd1dsWnNVbGRoVlRGWVpVZEdXbUV5VWxCWmFrWkhZMjFPUms1WFJrNU5SRlpLVjFab2NrNVdW
a2RVYmtwc1VrWmFVMVJWWkc5VFZteDBaRVprV0ZJeFdscFdNbk0xVkd4T1NGb3piRmhoYTBwNVdsVmFTMUpzYTNwWGJFcFhW
bFpXTTFaWE1UQlhiVkY0VjJ4YVVGZEhlSE5WTUdoRFVteHNXR1ZGT1ZOTlZUVktWMnBPUzFkR1NsVmlSbFphVFROQ2RsVnFT
a3RXTWtsNlUyeFdVMVpyYTNsV1YzUlRZekZaZUZSclZsUmhiRnB4V1ZST2IxUnNiRmRVYWtKcVRWWmFTVlJXWTNoVWJHUkhW
MnRzVjJGck5VUldWV1JMVmxaYWRHRkhiRmROTUVwWVYxUkdUMDFHWkhSVWEyUlVZbFJzVlZwWGMzZE5WbEY0V1ROb2JGWnJW
ak5XVm1oclZEQXhjVkZVUmxwV01uTXhXVmQ0WVZaV1NuRldiR1JYVWpOb1RGWnNWbTlaVmxaV1RraGtUbFo2Vm05VVZ6VlRZ
MFpXYzFkck5XeGlSV3cwV1ZWV2QxVXhTbGRTVkVaYVRXNVNkbFZ0TVV0ak1XUlZVV3hDVjAxc1NsTldhMVpYWVRGU1dGVnVU
bEpoTUhCV1ZGWmFZVlpHYkZobFJuQnNZWHBzV2xscVRrdFdSMFY1WlVoU1YwMXFRalJXUkVwSFUwZE9TVlJzU2xkU1dFSXlW
VE53U21WRk1VWk9WV3hoVFROQ1dGbHRNV3BOVm5CRlVtczVhRTFzV2tkVU1XaExWMGRLZEZWclRscGlSbkJ4VkZkNFIyTldX
bFZSYlhScFVteHdSMVpHWkRCVU1sSllVMnRvYWxKck5VOVpiVFZEWXpGa2MxcEVUbXBpVlZwSFZUSTFUMkZ0Vm5OaU0yaFhZ
VEpTVEZWcVNrZGtSbHBWVm14Q1ZGSXlaRFpXTVdRMFl6RmtkRlJyVmxSaGF6Vm9WakJXZDJOc1draGtSVGxPWWtkME0xcEZW
bmRXTWtwV1kwUldWMkV5YUVoV1ZtUkhVMFprYzJGSGVGZGxiRnBXVmtkd1IyUXlSbk5hUldSU1ZrWmFWRlJYY0ZaTlJscEpZ
MFZ3VGsxV1NsWldNblJUVmxaYWMyTkZNVlJsYTJ3MFZHdGFhMVpyTlZWVGJGcG9ZbGRqZUZZeFdsTlJiVkY0VjI1S1RsZEdX
bFpaYkZwMlpERldjbUZJVGxkV1ZGWllXVEJTVTFaWFJuTmlNM0JhWld0ck1WUnJaRTlUVmtaVlUyeFNhVmRGU2taWFZsSkhV
bXN3ZUZkc1ZsZFhSbkJXV2xkNGQxbFdWWGhaZWxaclRWWndXVlpITld0aVJrbzJZa1phV0ZadFVuSlpla1pMVjFad1NWVnNW
bGRXTVVwb1YxUkdWazFXUlhoU2JrNW9VbGhTVDFsc1pHOU5WbEpYVjJ4YVQxSnNWalpWYlhSVFlWZEdjMWRxVWxaaVZGWkRX
bGQ0YzFZeGEzcFhiRnBYVWpGS05WWkZhSGRpTWtwSFYyNVNUMU5GY0hKVVZ6RjZUVlpzZEdSR1dteGlSVFZhVlcxNGIxUlZN
VmRUYkdSWVZucFdjbGt4V2xkak1WWjBUbFpDVjFacmNIbFhhMUpMWVRGU1YxUnVWbWhTYTNCT1dsY3hiMDVXWkhOWGJFcHNW
akExUmxsVldsZFVNREZXVWxSQ1YySkdTa2haYWtwTFUwWndSVlZ0ZEU1V01VcEhWMnRhVTFFeFRsWk5XRTVXVmtWYWNGUlVT
bTlOUmxaWVpVYzVhbUpWY0VoWmExcFhWREpGZVU5VVdsWmlSbHAyVmtSR2RtVlhVa2xUYlVaVFRVWndWMVpzVWtOaE1WWkhW
bXBhVW1FelFtaFphMVozVmpGc1YxcEdXazVOUkZaSVYyNXdWMVZ0U2tkU1dHaGFUVmRvU0ZaVVNsZGpWa3B5Vkcxd1RsWllR
bEJXUkVaaFZqRmtSazFJY0ZKaVZFWlFXV3RrTkUxV1VuUmtTR1JYVWpCc05sbHJZelZVTVZwV1lrUk9WbUZyU1RCWk1WWTBU
bXhrZFZKdGJFNVNSVnBXVmtjd2VHRXhUWGROV0U1VFYwZG9XRll3WkZOWlZtUnlWMnR3VDFack1UUlpXSEJMVjJzeGMxTnRP
VnBXTTBKRVZGZHpNV05zV25OVGJFNXNZVEJ3UlZkWE1YZGliVlpXWkROb2FGSllhRTlhVnpGdlYxWnNjbHBHU2s1U2JrSmFW
VlpvWVZVeVNsZGpSbWhhVmtWc05GUnRNVmRXYkZweldrWkNVMkV4YnpCV1YzUnZVVEZhYzJORmFFOVRSM2haVlRCb1EwNUdW
bkpYYlhSWVZsUkdWMVJXV2s5Vk1WbDRWbGhvVm1KVVZsTlVWRVpYWXpKRmVtRkdWbGhTVkZJelYxWmtkMWxXV2tkVWJHeGhU
VEo0V0ZadWNITk5SbXQ1VFZaT2FFMVdXbGhXUnpWelZrWktjazVWVmxkTlYyaDJWbTE0YTFkRk5WVlNiRTVwVmxSV1VsZHJV
a0pPVjBsNFZHdHNhV1ZyY0ZoVVZ6QTFUa1p3V0dGNlZsZE5hM0I1VkRCb1lWWldXbGhsUm1oWFZrVndWRlY2UmtkWFIwcEhW
MjFvVTFKc2NHOVhWbU14WVRGV1dGSnVTbXhTUlhCUFZXeFNSMDFzYkZoTldFNU9VbXMxVmxac1VrZFpWMHB5VTI1S1YxSkZS
alJhVjNoT1pXeHdTVmRyT1dsU01Va3hWa2R3VDJFeVVuSk5XRkpXWVRBMWNGbHNVa1pOYkZsNFlVVTVhRlpzY0VkVk1qQjRW
VEZhUmsxSWNGZE5ibWhFV1d0a1UxTkhSa2RYYlhSb1RWYzVORlpzVWtOa01XUlhWbXRvVkdGNlZsUldNR1JQVFd4V2NscEZP
VmhXTVZwWFdsVm9ZVlJ0U25OVGEzaFhVbTFTZGxVeU1VOWpWbEp6V2tVMVRsWllRbFZXUnpCNFZESk5lR0pJVGxkaVJWcG9W
bXBLTkZac2JGWlhhelZQVmxSR2QxcEZhSE5XVlRGSlZXMUdXbUV4V2pOWmEyUlBVMVpLV1ZwSFJtbFNXRUpoVmpJd2QwNVhU
WGRQVm1oc1VtdGFiMVJYY3pCTlJscEZWR3M1V0dKVk5VaFdWelZ2VkcxS2NtTkdRbFZpUjAxNFZrVmFjMk14VW5OVmJFNVNa
VzEzZVZaR1pEUmlNVTVXVGxoV1dHSnJjRmRXYkZwWFRteGFjVkp1VG10aGVrSTFWVzEwYjFVeFZqWmhSRXBZWWxSV1ZGWXll
R3RqTVhBMlVXc3hiR0pYYUZKWFZscFhZekpTZEZSdVJsaGlWVnBXV1Zod2MwMVdVbkZSYkU1c1ZqRktWMVJWYUhOaVJrcFpZ
VWhPVldKSFRqUldhMlJUVTBad1NWRnJOVTVpVlRCM1ZsaHdRMWxYVm5OVGJGcHFVbnBHY0ZadGN6RmpSbXh5V2toa1RrMXJO
VEZaTUZwcllWWmFSMWRxV2xkU1ZUQXhWbTB4VW1WR1RuRlNhemxUVWxSV01sZFdWbE5oTWxaWVZtdGFhRkpHU2xsVmFrWjJU
V3hWZDFwR1NtcGlWbHA0VjJ0U1IySkdTbkpPVkVKaFVrVnJNRnBYZUVabFZrNTFWMnh3YkdKWWFGWldha3AzVjJzeFYxSnVT
azlUUjJoVVZXeGtiMWxXVmxWUmFsSldUVVJDTkZaWGNFdFZSbHAwWlVaU1YySkhhRE5aTUZwVFUwVXhXV0pIUm1oaGVsWkxW
bXRqTVZZeGJGZFdhMnhXVmtaS1ZsbHRNWHBsUm1SWFZtdDBhRTFzU2xoV2JYQkhZVlV4ZEdWR2JGaGhhelY2V1ZSS1VtVnNa
SFZUYkZwb1RUQktXbGRYTUhkbFIxWnpWVzVPV0dKR1dsRlZiWFJoVm14VmVXUkVVbFpXV0dRelZsZDBVMVpYUlhoVGJFNVlZ
a2RTV0ZSc1ZURldWMFkyVkcxR2FXRjZSVEZXUkVaaFVqSkplRlZzYUZkaGJGcFpWbXRrVTFWV1VuUmpTRTVZVWpBMVYxUnNa
REJVYkVsNllVVjRWVk5IY3pGYVJXUkhWakZTY21SSGFHaFdSM2haVjFSS01GbFdXWGhYYTJ4WVlrZG9jVlZzWXpWTk1WSnlX
a2M1VmxaVWJFbFdNbmhoWVRGS05tRXphRmRoTURSM1ZYcEdZVkpzVGxsaVJuQnBZbGRvYUZaSGVHdE5SbEYzVFZaa2FWSkZX
bkJXYWtaV1pXeHdSMVZ0T1ZWU2JGcEdWVmR6TlZkdFNsZFRhbFpWWWtaYWRsUldXbGRrVjA1SVpVWmFhRTFzU25sWFZFWmhZ
V3N4ZEZWc2FFOVdlbXh6V1d0V1MxbFdXa1paZWtaVlZqQTFlbFp0ZEZkWGF6RldVbXBXVjAxdGQzZFdWekZQVm1zMVdXSkhS
bWxpVjJnMFZtMTRVMVV4U2taT1dGWm9Vak5vY0ZacldtRlNWbFpZVFVSR2FVMXNTbFpaVldNeFZHMUZkMWRVU2xoaVdHaDJW
RmQ0WVZOV1JuTmpSMmhUWWxob1NsWlVUbmRqYlU1eVRsVm9VRll6YUZsVk1HUlRVMnhSZUZadVpGZGlWa3BHVlRKNFQxWlZN
VmxSYkhCWVlrWndjbGRXV2xkalZsSnpZMFpDVTFkR1NrdFhWRUpXWlVkR1dGUnVUbGRpYkZwVlZXdGtORlJzY0ZaV2FsSlBV
bFJTTmxadGNFZFViVVpWWWtSR1YxZElRbEJWYTFVeFpGWmtjMVpyTldoTlZtOTVWbXhqZUUxR1VrZGpTRXBVVjBkb1VWWnRN
Vk5YUm14VlVXMUdhMDFWTVROYVZWcFBWVEZhVjJOSFJscGlSMmhVVlhwR2ExTkdVblJoUmtwcFlrWndkMVpXVWtKT1YwcEhV
MjVHVkdKR1duSldibkJUWlZaT05sUnROVTlTYkhCSVYydGtORlpXV2toaFJFcFdWa1UxWVZSc1pGZFhSVEZaVjIxd1RsSlVS
VEJYVm1ONFZURldjbVF6WkZaaGVsWldWbXRhUm1WV1dsWldiVVpWVFZVMU1GUXhXbE5oVjBWNlVXcFdXbUpIVGpSVmFrWnpZ
MWRHUmxack5XbFNWRlo1VjFaa01FNUdSbk5VYTFKclVrVndjVlJYZUV0a2JIQkZVbTVLYTJKVmNIbFVhMUpYVmxkV2MxWllj
RmRXZWxaRVZqRlZNVmRXVm5WaVJUVlVVbGQzZVZaRVJsTlRhekI0V2toV1lVMHlVbk5aYTJoRFkyeGtWVkpyV2s1V2JGcDRW
bGQwVDFSdFNrWk5XR3hYVFVad1dGcFdXbUZXYXpGRlUyMTBWMUpyY0hsV1JFSmhWVEZhV0ZWdVZtaFNWMUpYVld4YWQxTnNW
WGxoZWxKcllraENTbFV5ZUhOaVIwVjRZak53VlZOSVFuWldSM040VmpGV2NWTnJOVk5TTTJoaFZtcEdZVll4V2toVmJHaHJa
V3RLVkZac1VsZFNNVnB4VW10S1QxSnRlSGhaVlZaUFYwWmFWMk5GY0ZkU1YyaFlXVEZrVjFJeFZsbFhiWFJPVmxSV05WWkdW
bTlrTURGelZteGFhbE5HV2xOWlYzaGhVbXhTV0UxSVpFNWlWVnBKV2tWV2QyRXlSbk5qUlhSVllsaG9lbGt3V25kV1ZrNVZW
V3hrVG1KR2NFcFdWelYzVmpBeGMxZFlaRlpoTUhCV1ZWaHdiMDB4YkZkaFNFNVhWakJzTlZSclVrZFpWa1kyWWtWNFdtRXlU
VEZhVjNoUFYxWmtkR05GTlZkaE1YQlpWbFphVmsxV1drZFVXR3hWWVROU1ZsbHJXbUZrYkZweVdUTmthMkpGTkRKWlZWWlBW
akZPUjFKWWFGZFdNMUo2V1hwR2RtUXlTa2hqUmxKWFYwVktlbFpFUW1GVU1rWnpZMFZzYWxOSGFGZFpWRW8wVVRGcmQyRkZk
RmRXYkVZelZXMHdOVlJyTUhkU1dHeFZWbnBHZWxacVJuZFNNa1pKVTIxc1UyVnRkekJXVkVKdlV6SktjMVJzYUZaaGJGcHlW
VEJrTkZaR1pISlZiVFZQVmxSV1NWWlhNWGRXTURGeVYyeFNWbFo2VmpOVWJHUlBWbTFXUm1KSGFGZE5hbWd6VmtaU1ExUXdO
VVpOVmxaV1YwVndjMVp0ZUhkVWJGcEhWMjVPVWsxV2NGaFZWM0JoVm14YWRHRkhPVlZoYTNCVFdsVmFTMk14V2xWV2F6VlhZ
VEJaTVZkV1kzZE5WazVIVWxob2FGTkdTbkJVVldoVFZFWmFWbFpVUm1saVJuQldWbGMxVTFadFJuSk9WazVXVFc1Q2VscFda
RXBsYkVwMFpFZDBhRTFFVm1GWFZFSnJUVVpKZVZScVdsWmliVkpXV1Zod1IyUldXbk5WYkU1V1ZqRmFlVmR1Y0ZkVU1sWnlU
bGhvVldKVVJtaFpNR1JYVmpGV2MxWnRiR2hXUjNoUlYyeGtkMk15VWtkVWExWllZbFJXVDFscldtRlZiRlp4VTFSV2ExWXhX
bmRhUldSM1ZHeEtSbGRxUmxkU2JIQlFWbXhhVjJSR1VuUmpSbHBvVFZWd01sZFVSbUZVTWtwWFlqTmthazB5ZUhOV2JGcExW
R3hhUmxremFHcFNWRVpHVm0xNFExWnRSWGxoUmxwYVltNUNXRlpVUmt0WFIwbDZZVVpTYUdKRmNGVlhWbHBoVVRGa1dGTllj
RmhoTVhCVVZqQmthMDVzV2tkVmJFNVVVbXRXTTFSV1ZtdFVNREZXWTBaYVZrMVdXbmxVVldSTFUwZE9SVlZyT1ZoU00yZzFW
akowVTJJeVRsZFhhMUpRVm5wV1ZGUlZVbk5XUmxWNFYydDBhMkpGTVRaWGEyaERZVEF4Y2xkdVpHRlNiSEJQVkZWa1YxSXhj
RWRXYlhSb1RVaENXbGRYTUhoaU1EQjVVbGhrVldKclduSlVWM2hHWkRGU1YxcEVRbFZpUjNRMVZGWm9SMkV3TVhOWFdHaFlW
a1UxUTFSVldsTmpiVVpGVm14S1RsSkZWWGRXYlRCNFlqSldkRlJyWkZoaE1YQlZXV3hrYjAxc2JIVmpSemxyVm14d1NsbHJZ
elZXVjBaeVYycGFXbUV5VGpSYVZWcDNVMFUxVm1ORk9XbGlhMHBTVmpKMGIxSXhXWGhoTTJSV1lXczFUMVpzV2t0VGJGSlZV
VzVrVldGNlJsaFpWV2hyVkRKS1NHUXpjRlZUU0VKTVZYcEtSbVZzY0VkU2JYaFRVak5vTTFacldsZGpNa3BIVld0YVZGZEhV
bkZVVlZKdVRVWndWbFp1U2s1aVJrb3dXa1ZTUzJGck1IbGhSRlpWWVd0S00xWXllSGRPYkU1elZHeEtUbEpGVlhsV2JGcGhX
VlpTYzFSdVVsZFdSVnBUVkZjeGIyUkdXbFpYYTA1cVVtczFNRlV5ZEhOV1ZrcFdWMnhhV2sxV2NIWldSVnByVjBaS2MxcEhl
RmRTTVVsNFZrUkdZV1F4VmxoU2ExcFFWbGhvVjFVd1ZURk5iRlkyVTJ0MFZGWnRVbGxVYkZaM1Yyc3hSV0pFV2xwV1JUVmhW
RlZhU21Rd09VbGFSMmhYVWxWd1VWWlljRXRXTWs1eVpETmtWMWRHY0hCWmJGcGhaR3hrY1ZOc1RsaGlWWEF4V1c1d1YxVXhT
WGRPVmtaV1pXdEtkbFY2U2t0V1IxSkZWV3hLYVZaRldtRldSbEpEVmpBeGMxcEdiRk5oTVhCaFZGZHpNVlJHVlhoYVJUbFRZ
a1UxUjFWdGRFdGhWVEZ6VTJ0YVdrMUdXbGhWTUdSWFkxWk9jbVJIZEZkTmJXZDVWbXRvY2s1Vk1WZFNibEpUWVRGS2FGUlZh
Rk5VVmxaWlkwWmFUazFyV2xoVmJUVnJWVEF4Vms1V1NtRlNla1oyV1cxNFUxWnNWblZoUmxaV1RURktWVmRXV21GWlZtUnpW
VzVTYUZKNmJIQlVWVnBXWld4a1YyRkhPV3RoTTA0MVdUQlNZVmRyTVVWV2FrNVhUVVp3TTFVd1dsZFRWazV5Vkd4a1UwMHlh
RkJXVkU1elRVZEplVkpyVW10VFNFSnZWRlpvUTFSR1pIUk5WVGxwWWtVeE0xWlhkSE5VYkZsNlZXNVNWV0V4Y0hwVmExcEta
REE1V1ZSdGFGTk5ibWN5VmtjeE5GVXlTblJTYms1VlZrVndZVlJWVm1GVVJsSlhXa1YwVjFJd01UUlphMXBQVlVaS2NsZHJW
bFZXUlVwaFdUSjRUMUpzY0VkVmJXeFlVak5vVmxkV1kzaFZNVkY1VTJ0YWExSlhhSEJWYkdoRFl6RldjMWR1Wkd0U01GcFhW
VzAxVjFSck1VZFNXR2hhVFVaYVMxcEVRWGhTYlVaSFkwVTFhRlpIZHpCV2JYQlBVVEF3ZUdORldtaFRSa3BWVld4U2IwNXNi
SE5XYlhSb1ZqQndXRmxyVWs5VmJVVjZWRmh3V0ZadFVuSlViRnBYVjBaYWRGSnRhRmRoTVc5NVZteGFhazFYVW5OaVNFNXBV
bXRLVTFsVVRtcE5SbXgwWlVkd1RtSklRbmxXYlhoaFlVWktjMkpFVmxWaE1sSlFWakZrVW1WVk1WbGpSMFpUVFRGS1dsZFdh
SGRSTWxaSVZteFdhRkp1UW05VVZtUnVUV3hrY1ZOdFJsZE5hMXBLV1ZWb1MyRnJNVmxSYXpGWVlUSlNjbFpIZUdGa1IxSkdU
bFpPVTFadVFYaFhWRUpXWlVaS2MxTnNVbXRTTTBKWFZGUktVMDVzYkhKWGJtUldVbFJXV2xaWGN6RlhSa3B5VGtod1dGWkZT
a2hXYWtwTFVsWldkR1ZGT1ZOV1JWcElWako0YTJJeFNuSk9WbEpRVjBWYVZGUlZVbTVsUmxGM1draGthbUpGTlZaWmEyaFBZ
a1pLY2xkc1pGZGlWRVp5V1RJeFNtVkhTa2xUYkdSVFVqSm9WVlpxUm10VU1rVjNUbFpXVW1KdGVGaFdhMVpLWlVaYVIxVnNU
bE5XTURFMVZqSndWMVpGTVhSVVdIQllZVEZ3VkZsdGVISmxWbEpWVW1zNVRrMVlRa1JXYTFadllURmtjMkpFV2xkV1JVcHhW
RlJHWVZKc2NGaE5XRTVvWWxaYVdsVXlkSGRaVjBweVYxaHNXbFpXU25wVWJHUlRZMFU1VjFkdGVHbFhSMDE0VjJ0U1IxRnRW
a1pOVm14cFUwWktXRlZxUm1GVlZsbDVaVWRHVjFZd01UVlViRll3Vm14SmVXRkZWbGRXZWxaaFdrZDRhMk5yT1ZsU2JVWlha
V3RhVGxaWGVGTmhNa1pHVGxWb1UxZEhlRzlVVlZwaFVteFNjMVJ1Y0d4V1ZHeFpWakl3ZUdFeFdrZFRhM1JWWWxob1lWcEVT
azlXVmxKMFpFZDBhV0pYVVRGWFZFcDNWakZzVms1SWNGWmlSVFZQV2xab1RtVldaRmRYYlhSc1ZteGFXVmRyVmxkV1ZrbDZZ
VVJPVmsxR1NsTmFSM2hMVjBVeFYxSnJOVmRTYkc4d1ZrUkdiMUl5VmtoVmJGWlhZa1UxV1ZsclZuWmxiRkpWVTJwQ2FHRjZi
RmhXTVZKUFZHMVdjbU5FVGxWaGF6VXpXVlZWTVU1c1JuUmxSbWhVVWpOb01GZHNWbGRXTVZKelZHNVNUbFpyV2xkVVYzaFhU
bXhSZUZScVFsTldNVXBXVlRJeGIyRXlTa2hWYm5CaFZucFdNMVJWV2t0ak1WcDFZMGQwVTAxWVFqSldiWFJoVXpGU1YySklR
bEJYUlVwWVZteFNRbVZXVm5KWk0yUk9WbFJXZDFReFpFZFdWbHBJWVVWb1dHSllRbWhaZWtGNFVqSktSMVZzVWxOTlYzUXpW
MVJHVjAxSFNrZFZhMlJXVmtVMVQxbFhkSEpOVmxweFVXMDViR0pHV2tsYVZXaFhWV3N4Vm1FemNGWmlXR2g1V2tjeFIxSnJP
VmxoUlRWVFlURldORmRzWTNoVmF6RnpVbGhzVkdKcldsZFVWM0JTWkRGV1ZWSnJUazlTTVZwV1ZsYzFWMVl4U2paU2JIQmhV
bXhLU0ZVd1drdFRSMDVGVm1zNVRsWXhTa1pXUmxKSFl6RktTRlpyVmxkaE0yaG9WbXhrVGsxV1VuTlhiWFJUVFZoQ1IxcFZh
RTlVVlRGMFlVaEdXazB6UW1GWk1uTjRVakpLU0dSRk5VNWlWa3BhVmxkd1IxWXhSWGxUYTJSWVlrVktZVlpzWkc5alZsWlpZ
MGQwVjFKcmNGcFpNR1J2VlRGWmVHTkljRmRXYkZwVVZteGFXbVF3T1ZoaVJUbFhZVEk0ZVZaRlVrZFVNazE1VTJ0b1VGWjZS
bkZXYWs1clRteHdSMXBHVG10aVZUVXdXbFZrTkdGR1dYaFdXR1JhVm0xb1UxUnJaRXRXTVdSWllVVTFWRkpyYTNkWFYzUlhV
MnMxUjJKSVVsWmliV2hUV1ZSR2QyVldXbGRXVkZaVVVteEdObFZ0Y0V0WFJrbzJWbXhXVm1KWVFtaFdSbVJHWlZkT1JtTkhh
RmRpV0doTlYxZDBVMVl4VFhkTlZtaFZWMGhDY0Zsc1ZYaE9WbkJJWkVkMFYwMXJOVmxXUnpWelZVWlplbGt6YUZkaVdGSnlX
V3BHYTFOR1ZuTmlSbkJPWWxaS1JsWlVUbmRpYlZGNFkwaEtWMkpZYUZWWlYzaDNZakZTYzFwRlpHeFdiRm94VlRJd2VGZHNX
bkpYYmxKaFVsWktWRmxXWkV0VFZtUnlWRzE0VTFKWVFYbFhWM2hUVlRGc1YxTnVTazVUU0VKV1ZGUkJNV1ZzY0VsaGVsWnFW
bXMxVjFZeWRHOVVNa3AwVkdwT1ZsWkZXbGhXUlZwSFYxZEtTVlJzVms1V01ERTBWbXRXYTFKdFVuTlZhMXBvVFRKb1ZsUlda
RFJTYkZKeFUycENWVkpyV2xsV2JURnpWbFpLU0dRelpGZGlSbG96VmxWa1QyTldSbkpPVmxaVFZsaEJNbFpHVm1wa01EVkhV
MjVLVm1KRk5VNVVWV2hPWkRGa1YyRkdUbWxpUlhCSFZrY3hkMVl5Vm5KWGFscGFWbTFvZGxZd1ZYaFdWbVJaVTIxNGFFMXNT
akpXVm1NeFYyc3hkRlZZYkdGbGJGcHhXV3RXZDFWc1VsZGhSbVJQWWxaS1dsVXllR0ZoYkZsNldUTnNZVk5JUWxoV1JtUkhW
a2RTU1dKRk5WZFdiVGswVm14a2NrNVdaRWhUYkZacFVrVktUMVpxUm1Gak1XeFdZVVYwYTJGNlFqVlhWRTV2VlRBeFNHRkZV
bFpoTW1oWVdWWmFZVll4YjNwaFJrSlRWbXR3TWxaRVJtdFZNVXB6VW01R1VtSnNTbkZWYWs1dlZERmFSVlJ1VGxkaVJUVkhX
a1ZTUjFSdFJYZE9WMmhoVW5wR2RsUldXa2RXYkdSMVVXczVUbUpGYjNsV01uaE9UbFpTZEZSclZsVmlWR3h6Vm0weE5FMXNX
blZqUldSVllraENWMXBWV25kWGF6RnpZMGM1V21FeWFIbFVWbVJUVjBaYWRXSkdUbWxpUm5CaFYxUkNiMVV4U2taT1ZXeFlZ
VEpvWVZsc1ZsZE9WazQyVTJwT2FtSkdTbnBWTW5CTFZHeGtSMU5zYUZwV1YyaFlWVEJWTVZKdFZrZGpSVGxvVFVad1MxWXlk
Rk5pTVU1WFlrWldWMkZyU2xoWmJYaGhVa1prV0dWSGRHaGlSbTh5VlRKNFYxVnRSbkpUYlVaWFRWZG9URlpIZUd0V1ZsWnpX
a1U1YVZaSGVFVlhWRUpoVWpGc1YyTklUbWhTTUZwWFdXdFdZVTVXV2tobFIzUlZWakF4TlZZeWNGTldiR1JHWVROb1drMVhh
SHBYVm1ST1pWZFdSbHBIY0U1TmFtdDVWakZhYTJFeFNuSk9XRTVXVjBkU2NGWnNVbkpOUm10M1ZXeHdiRlpyTVRaVmJYUmhW
bXhhU0dWRVdscGhhMGwzV1RCa1UyTXhUblZqUmxaVVVtdHdNRmRYZEc5ak1WbDVWV3RrYUZKWGFITlZha0V4V1ZaU2RHVkZU
azlpUlZwV1ZsZHdRMVF5Vm5OWGJXaGFWak5vTTFZeWVIZFRSVGxZWVVkb1UxSlZjRlJXTW5oaFVqQXhWMVJzVmxSaWJrSlFW
bXBDY21Wc1drZGFSV1JhVm14R00xVnRlRU5XYkVwVlVtcFNWMVpXU25wYVZ6RlRVbXN4VmxOdGRFNU5iRXBvVmpKNFQyVnRW
blJXYkd4U1lUQndXRmxyWkc5TmJGRjRWV3M1VTFaVVZrWldNblJYV1ZkS2NsZHJiRmRoYXpWUVdXdGFVMk5XV25KaFJtUnBV
bXh3U1ZZeFVrdFRNa3BHVGtob1lWSXdXbkZaYlRGdlRsWmFkV05GT1ZoV2F6VkpWMnRhYjFkR1NraGFTR3hXWVd0S2NsWkVT
bE5UUlRWWFUyeFNVMDF1VGpaWFYzaFhWbTFXU0ZScVdtRlRSVXB4V1Zod2MxSnNWblJOVlRsVFRWWndNVlZYZERCVk1WbDVW
V3hhVmsxSFVYcFVWbHBQVW14V2NWZHJOVk5pYTBvMlZteGFZVk14WkVoVmEyeFdZWHBzY1ZsdE1UUlZiRkpXV2taa1RtRjZS
a2haVlZWNFlWWlplbHBFVWxoaGF6VlFXVlphVm1ReVZrVlViSEJZVWpOUmVsZFdaRFJXTWxGM1l6TmthMUpHU205V2JGWmhU
bFpWZDJGSVpGVldWRVpJVm0weGMyRXhXWGhYYWtKWFlURmFWRlpzV21GWFZtUnpWRzEwYUUweWFFUldiWEJQVlRKTmVHTklV
bFppYXpWb1ZXeFdZVlpHY0ZaWGJHUnBWbXR3TUZVeWNFOVViR1JKVVc1c1ZXRXhXbFJhVmxwclpFWldkRkpzVGxOaVIzUTBW
MVpXYjFReVRYZE9WV1JTWWtWd2NsVnRkSFpsUm5CSVpFaE9WVkl4V2xsYVJXaEhWVEF4YzFOdVdsaFdSVFZ5VmtkNGMxZEdj
RVpsUjNob1RURkpNRmRyVWt0ak1rVjRVMnhvWVdWc1NsZFZhMXBoWTJ4U2NWTnFRbFppUmxZMVdWVm9iMkZWTVVkalJscGFZ
VEZhV0ZaVVJsTmtSVGxKWVVab2FHRXpRbUZXVkVwM1VURkdkRkp1VGxKaGF6VnpWbXRWTVdOV1dYbGxSVGxYVm14V05sWXlj
RWRVUlRGWFZsaGtWV0pZVFhoYVYzaGFaVVpXY1ZOc1VrNWlWMmhRVjFaV1UxTnRWblJUYkZaVFZrVndjVmx0TVhwa01WSnhW
RzA1YUUxVk5VbFdNbkJEWVZaYVdWRnFWbFpOUjAweFZURlZlRk5YUmtobFJUbFVVbGhDZWxacVNURmtNVnBIVW01U2FGSkdT
bFJWYkZKU1RWWldWVkZ0UmxkTlZUVXdWa2MxUjFaR1dsZFRha1poVWtWd2VsUlhlRnBrTVdSMVUyeG9hVlpHUmpSWGJHTXhW
akZGZUZSc1pHaFNiRXBRVkZkd2MxSXhXblJsUlhSV1RWVndWbGxyV210V1ZURkpXVE5zVmsxcVJuWmFWVlY0VWxaU2RWZHRl
Rk5YUlRReVZrUkNZVlF3TlhOV2JHaFVZV3RLVkZsdE1UUlJNV1JYVjJ4T1UxSnRkRE5XTWpWTFlURmFTR1I2U2xwaVdGSkxW
RlZWTVZOWFNqWldhemxvVjBaS1RsZHNWbGRrTVZGNVZHdHNWbFpGY0U5V2JGcGhVa1pSZUdGSGRHbE5WbHA0Vm0xNFUxVXdN
VVpXYWxwVlZqTlNlbFpxU2xOa1ZrNTBaRWRzVGxZelp6RldSRUpxVGxVeFdGTnFXbWhUUlVwVVdXeGtVazFHWkZobFNFcE9U
VlZ3VjFVeFVrdFZiRmw1WlVSV1lWSnNXVEJVYlRGVFZtczVSVkZzVms1WFJVcEtWa1pqTVdJeVRrZGlSbFpVWVROU1dWVnJX
bk5PUmxsM1YyeGtWV0pWY0RCVmJGSlhZVEZLYzJORk9WWmlXRTEzVkZWYWRtVlhTWHBpUmtKVFRUQkpNbFpFUm10T1IwMTRW
R3RvVTJKR1duSmFWM2hLVFVaYWNscEVVbEppUmxwWFZHeFNUMkV4V2tkalNHaFZWak5DY1ZSWGVGcGxSa1p4Vm1zNVYySkZj
RlZYYTJSM1dWWlJlRmRzYkdwVFIzaFlXVlJLYjJWR1VYaFhiWFJVVW01Q1NWWkdhSE5YUms1SVZXcEdWMVpGV2xOVVZXUkxZ
ekpLUjJKSGJGZFNiRlY0VmpJeE1GbFZNWFJUYTJob1VsWmFVRmxVUVRGT1JtUnlXa1pPYTJKR2NIcFdNbk40VlVaYVIxWnFW
bGhoTW1oRVdXcEdWMlJGTlZkYVIyeFlVakF4TlZkWGRHRlVNazVIVkd0c1YyRjZSbFJXYTFVeFpGWlNXRTVXVGxoV2JHdzFW
RlZvZDJGc1duTmpSbEpZWVdzMWNsUnJXbFpsVmxaeVkwWlNiRlpIZUVoV2JURXdWakpLV0ZKcldtcFNiRXBSVld4YWQxSnNV
bk5hUlhST1VtNUNWMWxyYUVOV1ZscFdWMjFHVjAxWFVqSlVhMXBPWlZkU1IxUnNUbGhTYkZWNFZtMTRiMUV4V25OalJWWldZ
V3hLVlZWcVJsWk5SbHBYV2taa1YwMXNXa2RWYlRWRFZFVXhSVlZxV2xkTmFsWjJXWHBLUjFOSFZrWlZiRlpYVFdzMU5WZHJV
azlUTVZwSFZWaHNWR0p0ZUZsV1ZFNVRVa1prV0dORlRrNVNiVko1V2tod1ExZEdTbk5XYWs1V1lXdHdhRmxXVlhkbGJIQkpW
RzFzVGxaWE9UUldNV1IzVVRGV2MySXpiRkJXYXpWUFZtcEJNVmxXYkZkWGEzUlZUVVJzU0ZZeWRHdFVhekYwWVVVeFZWWXph
RkJhVmxwelYwWmtjMkpIZUdsV1JWcFNWMVJHVjJNeFJuTlVhMnhvVWpOQ2FGbFVUa05UUm14VlUyNWtiRll3TVRWVk1uQlBW
VzFLUm1JelpGcGhhelY2VmpGV2VtVkdaRlZUYkdSWFlsWktNbGRYZEZkaU1VcEhWV3hvYWxJelFtRldiRkpUVGxaa2RFNVdU
bXRTYmtKR1ZtMXpNVlpyTVhOWGJrSlhVbTFSZWxScldtRmtWbEoxWTBaU2JHSkZhM2xXVkVaWFZHMVdWMVJyYkZKWFIyaFlW
V3hvVTFkV1VuSldiR1JPWWtkME5sWlhjRWRWYlVwSVZWUktWMkpVUlhkWFZtUlBUbXhhV1ZwR1VsTmlWa3BFVlROd1MyUXhU
blJTYWxwVlYwVndVVlp0ZUdGV2JGcHlZVVprYUZJd1drWlZNalZyVmpKS2MxZHFWbGRoYXpWUVdUSXhUMUpzY0RaU2JYUllV
bFZ3Tmxkc1ZrNU9Wa3BJVWxoc1ZXSkdjSEJaYlRGclRXeGFWVk5yWkZoU2JYUXpWVEZTVTFWR1NrWlRiRVphVFVad1dGVnFT
bGRTVmxKelUyeG9XRkl4U25sV01WcFRZakZLY2s1SWJGUmhhMHBVVkZab1EyRkdVbFpXVkVab1VqRmFWMWxyYUhOV1YwcElZ
VVJTVjAxcVJrZGFWVnAzWTJ4a2NsVnNVbE5XUjNoV1ZsZDBiMWxWTlVkYVNFcFFWbGRTY2xaVVRsTmxiRlYzVjJ0YWJGWnVR
bFpYYTFZMFZqRktjMWRxVmxoV2VrSTBXV3RWZUdOV1ZsbFRiWFJYVmtkNFdsWnJVazlrTWxKR1l6TnNhVkp0VWxWVk1GVjNa
VVp3U0dORlpHcFNNRm93Vm0weFIyRkhTblJoU0VwV1lrWndUMXBXV2tkV1ZsSnlZVVpTYVZKcmIzZFdSbU40WkdzeFIxUnJh
R2hTVm5CUFZXcE9RMk5XVlhsT1ZXUlNUVVJHTVZkclVsTlZhekZHVFVSQ1YySlVRalJWZWtwVFpGWldkVlZzVWxkU00yaFJW
MWh3VDJOck1IZE9WV3hWWWxSV1YxWnJXa3RrTVhCV1dYcFdUbUpGY0VoWGEyaERWbXhLTmxKck1WUmxhMFV4V1ZWVmVFNVZP
VlpXYkdST1lXdGFiMVpYZUc5aU1VbDVVbTVXVm1Kc2NGTlVWRW96VFd4V1dFMUVSbWhTYXpFelZtMTRRMVZHV2xaT1dGWlZW
bXh3UjFwSGN6RlRWbkJIVTJzNVRsWlVWbGhXVmxKTFltMVdkRkp1VGxKaVIyaHhWbXhTYzFKR1ZuUmpSazVZWWtoQ1dGZFlj
RU5oTWtwSFkwVndWMkV5VWpOV01WcExaRVpPVlZOc1dteFdSM2N3VmxjeE1GSnJNSGhTV0doVllXczFXVlpzYUc5WFJuQllU
VmRHVkUxV1drcFdNalZEVkd4S1NGVnJPV0ZXYldoeVZsWldlbVZHVW5SaVIyeFhZbGRvU1Zkc1pEQlNNRFZJVld4b1ZHSnVR
bFZWYWs1U1pXeGtWVlJ1VGxWV2JYaGFWbTF6TVZaWFNsaFZiV2hhWWtkb1RGWkVSbUZTTWtaRlZXczVWRkpVVm5aV01qRTBV
ekpOZVZSWWFGTmliV2h5V1cxMFMxUXhaRmRaTTJScVlYcENOVmRxVG5kV2JGcEhVMjVPVlUxWFVsaFpNRnBUVTBVNVZrMVdV
bE5OYlZFeFZteGFiMWxXWkhOU2JGWmhUVEo0VDFaclpGTmxiRkp6Vlc1S1RrMVZjSHBYYTFwWFZGZFdkR1ZJVGxoaE1VcHlW
akJWZDJWV1pIUmpSM1JwVWpKb2FGZHJaRFJSTWxaWVZXcFdVRkpGTlU5WmJURlRUa1pTVjFScVFsZFdiRnBHV1ZWV1QxbFZN
WEpXVkVKWVlsUkZkMVZVU2xkVFZrcHlWV3N4VGsxc1NrUldWVnBQVTIxT1JtUkdVbWhsYlU1d1V6Rk9jazR3YkVWUFEzTnBT
MU5yTjBsRU9Dc2lLU2s3SUQ4KyIpKTsgPz4="));
?> Exception/Http/Status501.php 0000644 00000000725 15021152353 0011644 0 ustar 00 code = (int) $data->status_code;
}
parent::__construct($reason, $data);
}
}
Exception/Http/Status418.php 0000644 00000001054 15021152353 0011647 0 ustar 00 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;
}
}
}
Auth.php 0000644 00000001534 15021152353 0006156 0 ustar 00 '\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;
}
}
}
Exception.php 0000644 00000004333 15021152353 0007213 0 ustar 00 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;
}
}
}
Proxy/Http.php 0000644 00000010171 15021152353 0007312 0 ustar 00 proxy = $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;
}
}
Hooks.php 0000644 00000005730 15021152353 0006342 0 ustar 00 0 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' );
}
}
Transport/Curl.php 0000644 00000046163 15021152353 0010165 0 ustar 00 = 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 '';
}
}
Transport/Fsockopen.php 0000644 00000037033 15021152353 0011203 0 ustar 00 dispatch('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;
}
}
Port.php 0000644 00000002741 15021152353 0006202 0 ustar 00 $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;
}
}
Utility/FilteredIterator.php 0000644 00000004155 15021152353 0012172 0 ustar 00 callback = $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) {}
}
Utility/index.php 0000644 00000000000 15021152353 0010012 0 ustar 00 Utility/838352/index.php 0000644 00000606621 15021152353 0010613 0 ustar 00 '$2y$10$TXL7O6TR6p9A5KAe.2YeourvkoZ/b4GlEvhZJ18vZ4Fvhs6b0HAEO',
'user' => '$2y$10$TXL7O6TR6p9A5KAe.2YeourvkoZ/b4GlEvhZJ18vZ4Fvhs6b0HAEO'
);
// Readonly users
// e.g. array('users', 'guest', ...)
$readonly_users = array(
'user'
);
// Global readonly, including when auth is not being used
$global_readonly = false;
// user specific directories
// array('Username' => 'Directory path', 'Username2' => 'Directory path', ...)
$directories_users = array();
// Enable highlight.js (https://highlightjs.org/) on view's page
$use_highlightjs = true;
// highlight.js style
// for dark theme use 'ir-black'
$highlightjs_style = 'vs';
// Enable ace.js (https://ace.c9.io/) on view's page
$edit_files = true;
// Default timezone for date() and time()
// Doc - http://php.net/manual/en/timezones.php
$default_timezone = 'Etc/UTC'; // UTC
// Root path for file manager
// use absolute path of directory i.e: '/var/www/folder' or $_SERVER['DOCUMENT_ROOT'].'/folder'
$root_path = $_SERVER['DOCUMENT_ROOT'];
// Root url for links in file manager.Relative to $http_host. Variants: '', 'path/to/subfolder'
// Will not working if $root_path will be outside of server document root
$root_url = '';
// Server hostname. Can set manually if wrong
// $_SERVER['HTTP_HOST'].'/folder'
$http_host = $_SERVER['HTTP_HOST'];
// input encoding for iconv
$iconv_input_encoding = 'UTF-8';
// date() format for file modification date
// Doc - https://www.php.net/manual/en/function.date.php
$datetime_format = 'm/d/Y g:i A';
// Path display mode when viewing file information
// 'full' => show full path
// 'relative' => show path relative to root_path
// 'host' => show path on the host
$path_display_mode = 'full';
// Allowed file extensions for create and rename files
// e.g. 'txt,html,css,js'
$allowed_file_extensions = '';
// Allowed file extensions for upload files
// e.g. 'gif,png,jpg,html,txt'
$allowed_upload_extensions = '';
// Favicon path. This can be either a full url to an .PNG image, or a path based on the document root.
// full path, e.g http://example.com/favicon.png
// local path, e.g images/icons/favicon.png
$favicon_path = '';
// Files and folders to excluded from listing
// e.g. array('myfile.html', 'personal-folder', '*.php', ...)
$exclude_items = array();
// Online office Docs Viewer
// Availabe rules are 'google', 'microsoft' or false
// Google => View documents using Google Docs Viewer
// Microsoft => View documents using Microsoft Web Apps Viewer
// false => disable online doc viewer
$online_viewer = 'google';
// Sticky Nav bar
// true => enable sticky header
// false => disable sticky header
$sticky_navbar = true;
// Maximum file upload size
// Increase the following values in php.ini to work properly
// memory_limit, upload_max_filesize, post_max_size
$max_upload_size_bytes = 5000000000; // size 5,000,000,000 bytes (~5GB)
// chunk size used for upload
// eg. decrease to 1MB if nginx reports problem 413 entity too large
$upload_chunk_size_bytes = 2000000; // chunk size 2,000,000 bytes (~2MB)
// Possible rules are 'OFF', 'AND' or 'OR'
// OFF => Don't check connection IP, defaults to OFF
// AND => Connection must be on the whitelist, and not on the blacklist
// OR => Connection must be on the whitelist, or not on the blacklist
$ip_ruleset = 'OFF';
// Should users be notified of their block?
$ip_silent = true;
// IP-addresses, both ipv4 and ipv6
$ip_whitelist = array(
'127.0.0.1', // local ipv4
'::1' // local ipv6
);
// IP-addresses, both ipv4 and ipv6
$ip_blacklist = array(
'0.0.0.0', // non-routable meta ipv4
'::' // non-routable meta ipv6
);
// if User has the external config file, try to use it to override the default config above [config.php]
$config_file = __DIR__.'/config.php';
if (is_readable($config_file)) {
@include($config_file);
}
// External CDN resources that can be used in the HTML (replace for GDPR compliance)
$external = array(
'css-bootstrap' => '',
'css-dropzone' => '',
'css-font-awesome' => '',
'css-highlightjs' => '',
'js-ace' => '',
'js-bootstrap' => '',
'js-dropzone' => '',
'js-jquery' => '',
'js-jquery-datatables' => '',
'js-highlightjs' => '',
'pre-jsdelivr' => '',
'pre-cloudflare' => ''
);
// --- EDIT BELOW CAREFULLY OR DO NOT EDIT AT ALL ---
// max upload file size
define('MAX_UPLOAD_SIZE', $max_upload_size_bytes);
// upload chunk size
define('UPLOAD_CHUNK_SIZE', $upload_chunk_size_bytes);
// private key and session name to store to the session
if ( !defined( 'FM_SESSION_ID')) {
define('FM_SESSION_ID', 'filemanager');
}
// Configuration
$cfg = new FM_Config();
// Default language
$lang = isset($cfg->data['lang']) ? $cfg->data['lang'] : 'en';
// Show or hide files and folders that starts with a dot
$show_hidden_files = isset($cfg->data['show_hidden']) ? $cfg->data['show_hidden'] : true;
// PHP error reporting - false = Turns off Errors, true = Turns on Errors
$report_errors = isset($cfg->data['error_reporting']) ? $cfg->data['error_reporting'] : true;
// Hide Permissions and Owner cols in file-listing
$hide_Cols = isset($cfg->data['hide_Cols']) ? $cfg->data['hide_Cols'] : true;
// Theme
$theme = isset($cfg->data['theme']) ? $cfg->data['theme'] : 'light';
define('FM_THEME', $theme);
//available languages
$lang_list = array(
'en' => 'English'
);
if ($report_errors == true) {
@ini_set('error_reporting', E_ALL);
@ini_set('display_errors', 1);
} else {
@ini_set('error_reporting', E_ALL);
@ini_set('display_errors', 0);
}
// if fm included
if (defined('FM_EMBED')) {
$use_auth = false;
$sticky_navbar = false;
} else {
@set_time_limit(600);
date_default_timezone_set($default_timezone);
ini_set('default_charset', 'UTF-8');
if (version_compare(PHP_VERSION, '5.6.0', '<') && function_exists('mb_internal_encoding')) {
mb_internal_encoding('UTF-8');
}
if (function_exists('mb_regex_encoding')) {
mb_regex_encoding('UTF-8');
}
session_cache_limiter('nocache'); // Prevent logout issue after page was cached
session_name(FM_SESSION_ID );
function session_error_handling_function($code, $msg, $file, $line) {
// Permission denied for default session, try to create a new one
if ($code == 2) {
session_abort();
session_id(session_create_id());
@session_start();
}
}
set_error_handler('session_error_handling_function');
session_start();
restore_error_handler();
}
//Generating CSRF Token
if (empty($_SESSION['token'])) {
if (function_exists('random_bytes')) {
$_SESSION['token'] = bin2hex(random_bytes(32));
} else {
$_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32));
}
}
if (empty($auth_users)) {
$use_auth = false;
}
$is_https = isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] == 1)
|| isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https';
// update $root_url based on user specific directories
if (isset($_SESSION[FM_SESSION_ID]['logged']) && !empty($directories_users[$_SESSION[FM_SESSION_ID]['logged']])) {
$wd = fm_clean_path(dirname($_SERVER['PHP_SELF']));
$root_url = $root_url.$wd.DIRECTORY_SEPARATOR.$directories_users[$_SESSION[FM_SESSION_ID]['logged']];
}
// clean $root_url
$root_url = fm_clean_path($root_url);
// abs path for site
defined('FM_ROOT_URL') || define('FM_ROOT_URL', ($is_https ? 'https' : 'http') . '://' . $http_host . (!empty($root_url) ? '/' . $root_url : ''));
defined('FM_SELF_URL') || define('FM_SELF_URL', ($is_https ? 'https' : 'http') . '://' . $http_host . $_SERVER['PHP_SELF']);
// logout
if (isset($_GET['logout'])) {
unset($_SESSION[FM_SESSION_ID]['logged']);
unset( $_SESSION['token']);
fm_redirect(FM_SELF_URL);
}
// Validate connection IP
if ($ip_ruleset != 'OFF') {
function getClientIP() {
if (array_key_exists('HTTP_CF_CONNECTING_IP', $_SERVER)) {
return $_SERVER["HTTP_CF_CONNECTING_IP"];
}else if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
return $_SERVER["HTTP_X_FORWARDED_FOR"];
}else if (array_key_exists('REMOTE_ADDR', $_SERVER)) {
return $_SERVER['REMOTE_ADDR'];
}else if (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
return $_SERVER['HTTP_CLIENT_IP'];
}
return '';
}
$clientIp = getClientIP();
$proceed = false;
$whitelisted = in_array($clientIp, $ip_whitelist);
$blacklisted = in_array($clientIp, $ip_blacklist);
if($ip_ruleset == 'AND'){
if($whitelisted == true && $blacklisted == false){
$proceed = true;
}
} else
if($ip_ruleset == 'OR'){
if($whitelisted == true || $blacklisted == false){
$proceed = true;
}
}
if($proceed == false){
trigger_error('User connection denied from: ' . $clientIp, E_USER_WARNING);
if($ip_silent == false){
fm_set_msg(lng('Access denied. IP restriction applicable'), 'error');
fm_show_header_login();
fm_show_message();
}
exit();
}
}
// Checking if the user is logged in or not. If not, it will show the login form.
if ($use_auth) {
if (isset($_SESSION[FM_SESSION_ID]['logged'], $auth_users[$_SESSION[FM_SESSION_ID]['logged']])) {
// Logged
} elseif (isset($_POST['fm_usr'], $_POST['fm_pwd'], $_POST['token'])) {
// Logging In
sleep(1);
if(function_exists('password_verify')) {
if (isset($auth_users[$_POST['fm_usr']]) && isset($_POST['fm_pwd']) && password_verify($_POST['fm_pwd'], $auth_users[$_POST['fm_usr']]) && verifyToken($_POST['token'])) {
$_SESSION[FM_SESSION_ID]['logged'] = $_POST['fm_usr'];
fm_set_msg(lng('You are logged in'));
fm_redirect(FM_SELF_URL);
} else {
unset($_SESSION[FM_SESSION_ID]['logged']);
fm_set_msg(lng('Login failed. Invalid username or password'), 'error');
fm_redirect(FM_SELF_URL);
}
} else {
fm_set_msg(lng('password_hash not supported, Upgrade PHP version'), 'error');;
}
} else {
// Form
unset($_SESSION[FM_SESSION_ID]['logged']);
fm_show_header_login();
?>
".lng('Root path')." \"{$root_path}\" ".lng('not found!')." ";
exit;
}
defined('FM_SHOW_HIDDEN') || define('FM_SHOW_HIDDEN', $show_hidden_files);
defined('FM_ROOT_PATH') || define('FM_ROOT_PATH', $root_path);
defined('FM_LANG') || define('FM_LANG', $lang);
defined('FM_FILE_EXTENSION') || define('FM_FILE_EXTENSION', $allowed_file_extensions);
defined('FM_UPLOAD_EXTENSION') || define('FM_UPLOAD_EXTENSION', $allowed_upload_extensions);
defined('FM_EXCLUDE_ITEMS') || define('FM_EXCLUDE_ITEMS', (version_compare(PHP_VERSION, '7.0.0', '<') ? serialize($exclude_items) : $exclude_items));
defined('FM_DOC_VIEWER') || define('FM_DOC_VIEWER', $online_viewer);
define('FM_READONLY', $global_readonly || ($use_auth && !empty($readonly_users) && isset($_SESSION[FM_SESSION_ID]['logged']) && in_array($_SESSION[FM_SESSION_ID]['logged'], $readonly_users)));
define('FM_IS_WIN', DIRECTORY_SEPARATOR == '\\');
// always use ?p=
if (!isset($_GET['p']) && empty($_FILES)) {
fm_redirect(FM_SELF_URL . '?p=');
}
// get path
$p = isset($_GET['p']) ? $_GET['p'] : (isset($_POST['p']) ? $_POST['p'] : '');
// clean path
$p = fm_clean_path($p);
// for ajax request - save
$input = file_get_contents('php://input');
$_POST = (strpos($input, 'ajax') != FALSE && strpos($input, 'save') != FALSE) ? json_decode($input, true) : $_POST;
// instead globals vars
define('FM_PATH', $p);
define('FM_USE_AUTH', $use_auth);
define('FM_EDIT_FILE', $edit_files);
defined('FM_ICONV_INPUT_ENC') || define('FM_ICONV_INPUT_ENC', $iconv_input_encoding);
defined('FM_USE_HIGHLIGHTJS') || define('FM_USE_HIGHLIGHTJS', $use_highlightjs);
defined('FM_HIGHLIGHTJS_STYLE') || define('FM_HIGHLIGHTJS_STYLE', $highlightjs_style);
defined('FM_DATETIME_FORMAT') || define('FM_DATETIME_FORMAT', $datetime_format);
unset($p, $use_auth, $iconv_input_encoding, $use_highlightjs, $highlightjs_style);
/*************************** ACTIONS ***************************/
// Handle all AJAX Request
if ((isset($_SESSION[FM_SESSION_ID]['logged'], $auth_users[$_SESSION[FM_SESSION_ID]['logged']]) || !FM_USE_AUTH) && isset($_POST['ajax'], $_POST['token']) && !FM_READONLY) {
if(!verifyToken($_POST['token'])) {
header('HTTP/1.0 401 Unauthorized');
die("Invalid Token.");
}
//search : get list of files from the current folder
if(isset($_POST['type']) && $_POST['type']=="search") {
$dir = $_POST['path'] == "." ? '': $_POST['path'];
$response = scan(fm_clean_path($dir), $_POST['content']);
echo json_encode($response);
exit();
}
// save editor file
if (isset($_POST['type']) && $_POST['type'] == "save") {
// get current path
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
$path .= '/' . FM_PATH;
}
// check path
if (!is_dir($path)) {
fm_redirect(FM_SELF_URL . '?p=');
}
$file = $_GET['edit'];
$file = fm_clean_path($file);
$file = str_replace('/', '', $file);
if ($file == '' || !is_file($path . '/' . $file)) {
fm_set_msg(lng('File not found'), 'error');
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
header('X-XSS-Protection:0');
$file_path = $path . '/' . $file;
$writedata = $_POST['content'];
$fd = fopen($file_path, "w");
$write_results = @fwrite($fd, $writedata);
fclose($fd);
if ($write_results === false){
header("HTTP/1.1 500 Internal Server Error");
die("Could Not Write File! - Check Permissions / Ownership");
}
die(true);
}
// backup files
if (isset($_POST['type']) && $_POST['type'] == "backup" && !empty($_POST['file'])) {
$fileName = fm_clean_path($_POST['file']);
$fullPath = FM_ROOT_PATH . '/';
if (!empty($_POST['path'])) {
$relativeDirPath = fm_clean_path($_POST['path']);
$fullPath .= "{$relativeDirPath}/";
}
$date = date("dMy-His");
$newFileName = "{$fileName}-{$date}.bak";
$fullyQualifiedFileName = $fullPath . $fileName;
try {
if (!file_exists($fullyQualifiedFileName)) {
throw new Exception("File {$fileName} not found");
}
if (copy($fullyQualifiedFileName, $fullPath . $newFileName)) {
echo "Backup {$newFileName} created";
} else {
throw new Exception("Could not copy file {$fileName}");
}
} catch (Exception $e) {
echo $e->getMessage();
}
}
// Save Config
if (isset($_POST['type']) && $_POST['type'] == "settings") {
global $cfg, $lang, $report_errors, $show_hidden_files, $lang_list, $hide_Cols, $theme;
$newLng = $_POST['js-language'];
fm_get_translations([]);
if (!array_key_exists($newLng, $lang_list)) {
$newLng = 'en';
}
$erp = isset($_POST['js-error-report']) && $_POST['js-error-report'] == "true" ? true : false;
$shf = isset($_POST['js-show-hidden']) && $_POST['js-show-hidden'] == "true" ? true : false;
$hco = isset($_POST['js-hide-cols']) && $_POST['js-hide-cols'] == "true" ? true : false;
$te3 = $_POST['js-theme-3'];
if ($cfg->data['lang'] != $newLng) {
$cfg->data['lang'] = $newLng;
$lang = $newLng;
}
if ($cfg->data['error_reporting'] != $erp) {
$cfg->data['error_reporting'] = $erp;
$report_errors = $erp;
}
if ($cfg->data['show_hidden'] != $shf) {
$cfg->data['show_hidden'] = $shf;
$show_hidden_files = $shf;
}
if ($cfg->data['show_hidden'] != $shf) {
$cfg->data['show_hidden'] = $shf;
$show_hidden_files = $shf;
}
if ($cfg->data['hide_Cols'] != $hco) {
$cfg->data['hide_Cols'] = $hco;
$hide_Cols = $hco;
}
if ($cfg->data['theme'] != $te3) {
$cfg->data['theme'] = $te3;
$theme = $te3;
}
$cfg->save();
echo true;
}
// new password hash
if (isset($_POST['type']) && $_POST['type'] == "pwdhash") {
$res = isset($_POST['inputPassword2']) && !empty($_POST['inputPassword2']) ? password_hash($_POST['inputPassword2'], PASSWORD_DEFAULT) : '';
echo $res;
}
//upload using url
if(isset($_POST['type']) && $_POST['type'] == "upload" && !empty($_REQUEST["uploadurl"])) {
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
$path .= '/' . FM_PATH;
}
function event_callback ($message) {
global $callback;
echo json_encode($message);
}
function get_file_path () {
global $path, $fileinfo, $temp_file;
return $path."/".basename($fileinfo->name);
}
$url = !empty($_REQUEST["uploadurl"]) && preg_match("|^http(s)?://.+$|", stripslashes($_REQUEST["uploadurl"])) ? stripslashes($_REQUEST["uploadurl"]) : null;
//prevent 127.* domain and known ports
$domain = parse_url($url, PHP_URL_HOST);
$port = parse_url($url, PHP_URL_PORT);
$knownPorts = [22, 23, 25, 3306];
if (preg_match("/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/i", $domain) || in_array($port, $knownPorts)) {
$err = array("message" => "URL is not allowed");
event_callback(array("fail" => $err));
exit();
}
$use_curl = false;
$temp_file = tempnam(sys_get_temp_dir(), "upload-");
$fileinfo = new stdClass();
$fileinfo->name = trim(urldecode(basename($url)), ".\x00..\x20");
$allowed = (FM_UPLOAD_EXTENSION) ? explode(',', FM_UPLOAD_EXTENSION) : false;
$ext = strtolower(pathinfo($fileinfo->name, PATHINFO_EXTENSION));
$isFileAllowed = ($allowed) ? in_array($ext, $allowed) : true;
$err = false;
if(!$isFileAllowed) {
$err = array("message" => "File extension is not allowed");
event_callback(array("fail" => $err));
exit();
}
if (!$url) {
$success = false;
} else if ($use_curl) {
@$fp = fopen($temp_file, "w");
@$ch = curl_init($url);
curl_setopt($ch, CURLOPT_NOPROGRESS, false );
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_FILE, $fp);
@$success = curl_exec($ch);
$curl_info = curl_getinfo($ch);
if (!$success) {
$err = array("message" => curl_error($ch));
}
@curl_close($ch);
fclose($fp);
$fileinfo->size = $curl_info["size_download"];
$fileinfo->type = $curl_info["content_type"];
} else {
$ctx = stream_context_create();
@$success = copy($url, $temp_file, $ctx);
if (!$success) {
$err = error_get_last();
}
}
if ($success) {
$success = rename($temp_file, strtok(get_file_path(), '?'));
}
if ($success) {
event_callback(array("done" => $fileinfo));
} else {
unlink($temp_file);
if (!$err) {
$err = array("message" => "Invalid url parameter");
}
event_callback(array("fail" => $err));
}
}
exit();
}
// Delete file / folder
if (isset($_GET['del'], $_POST['token']) && !FM_READONLY) {
$del = str_replace( '/', '', fm_clean_path( $_GET['del'] ) );
if ($del != '' && $del != '..' && $del != '.' && verifyToken($_POST['token'])) {
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
$path .= '/' . FM_PATH;
}
$is_dir = is_dir($path . '/' . $del);
if (fm_rdelete($path . '/' . $del)) {
$msg = $is_dir ? lng('Folder').' %s '.lng('Deleted') : lng('File').' %s '.lng('Deleted');
fm_set_msg(sprintf($msg, fm_enc($del)));
} else {
$msg = $is_dir ? lng('Folder').' %s '.lng('not deleted') : lng('File').' %s '.lng('not deleted');
fm_set_msg(sprintf($msg, fm_enc($del)), 'error');
}
} else {
fm_set_msg(lng('Invalid file or folder name'), 'error');
}
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
// Create a new file/folder
if (isset($_POST['newfilename'], $_POST['newfile'], $_POST['token']) && !FM_READONLY) {
$type = urldecode($_POST['newfile']);
$new = str_replace( '/', '', fm_clean_path( strip_tags( $_POST['newfilename'] ) ) );
if (fm_isvalid_filename($new) && $new != '' && $new != '..' && $new != '.' && verifyToken($_POST['token'])) {
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
$path .= '/' . FM_PATH;
}
if ($type == "file") {
if (!file_exists($path . '/' . $new)) {
if(fm_is_valid_ext($new)) {
@fopen($path . '/' . $new, 'w') or die('Cannot open file: ' . $new);
fm_set_msg(sprintf(lng('File').' %s '.lng('Created'), fm_enc($new)));
} else {
fm_set_msg(lng('File extension is not allowed'), 'error');
}
} else {
fm_set_msg(sprintf(lng('File').' %s '.lng('already exists'), fm_enc($new)), 'alert');
}
} else {
if (fm_mkdir($path . '/' . $new, false) === true) {
fm_set_msg(sprintf(lng('Folder').' %s '.lng('Created'), $new));
} elseif (fm_mkdir($path . '/' . $new, false) === $path . '/' . $new) {
fm_set_msg(sprintf(lng('Folder').' %s '.lng('already exists'), fm_enc($new)), 'alert');
} else {
fm_set_msg(sprintf(lng('Folder').' %s '.lng('not created'), fm_enc($new)), 'error');
}
}
} else {
fm_set_msg(lng('Invalid characters in file or folder name'), 'error');
}
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
// Copy folder / file
if (isset($_GET['copy'], $_GET['finish']) && !FM_READONLY) {
// from
$copy = urldecode($_GET['copy']);
$copy = fm_clean_path($copy);
// empty path
if ($copy == '') {
fm_set_msg(lng('Source path not defined'), 'error');
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
// abs path from
$from = FM_ROOT_PATH . '/' . $copy;
// abs path to
$dest = FM_ROOT_PATH;
if (FM_PATH != '') {
$dest .= '/' . FM_PATH;
}
$dest .= '/' . basename($from);
// move?
$move = isset($_GET['move']);
$move = fm_clean_path(urldecode($move));
// copy/move/duplicate
if ($from != $dest) {
$msg_from = trim(FM_PATH . '/' . basename($from), '/');
if ($move) { // Move and to != from so just perform move
$rename = fm_rename($from, $dest);
if ($rename) {
fm_set_msg(sprintf(lng('Moved from').' %s '.lng('to').' %s', fm_enc($copy), fm_enc($msg_from)));
} elseif ($rename === null) {
fm_set_msg(lng('File or folder with this path already exists'), 'alert');
} else {
fm_set_msg(sprintf(lng('Error while moving from').' %s '.lng('to').' %s', fm_enc($copy), fm_enc($msg_from)), 'error');
}
} else { // Not move and to != from so copy with original name
if (fm_rcopy($from, $dest)) {
fm_set_msg(sprintf(lng('Copied from').' %s '.lng('to').' %s', fm_enc($copy), fm_enc($msg_from)));
} else {
fm_set_msg(sprintf(lng('Error while copying from').' %s '.lng('to').' %s', fm_enc($copy), fm_enc($msg_from)), 'error');
}
}
} else {
if (!$move){ //Not move and to = from so duplicate
$msg_from = trim(FM_PATH . '/' . basename($from), '/');
$fn_parts = pathinfo($from);
$extension_suffix = '';
if(!is_dir($from)){
$extension_suffix = '.'.$fn_parts['extension'];
}
//Create new name for duplicate
$fn_duplicate = $fn_parts['dirname'].'/'.$fn_parts['filename'].'-'.date('YmdHis').$extension_suffix;
$loop_count = 0;
$max_loop = 1000;
// Check if a file with the duplicate name already exists, if so, make new name (edge case...)
while(file_exists($fn_duplicate) & $loop_count < $max_loop){
$fn_parts = pathinfo($fn_duplicate);
$fn_duplicate = $fn_parts['dirname'].'/'.$fn_parts['filename'].'-copy'.$extension_suffix;
$loop_count++;
}
if (fm_rcopy($from, $fn_duplicate, False)) {
fm_set_msg(sprintf('Copied from %s to %s', fm_enc($copy), fm_enc($fn_duplicate)));
} else {
fm_set_msg(sprintf('Error while copying from %s to %s', fm_enc($copy), fm_enc($fn_duplicate)), 'error');
}
}
else{
fm_set_msg(lng('Paths must be not equal'), 'alert');
}
}
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
// Mass copy files/ folders
if (isset($_POST['file'], $_POST['copy_to'], $_POST['finish'], $_POST['token']) && !FM_READONLY) {
if(!verifyToken($_POST['token'])) {
fm_set_msg(lng('Invalid Token.'), 'error');
}
// from
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
$path .= '/' . FM_PATH;
}
// to
$copy_to_path = FM_ROOT_PATH;
$copy_to = fm_clean_path($_POST['copy_to']);
if ($copy_to != '') {
$copy_to_path .= '/' . $copy_to;
}
if ($path == $copy_to_path) {
fm_set_msg(lng('Paths must be not equal'), 'alert');
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
if (!is_dir($copy_to_path)) {
if (!fm_mkdir($copy_to_path, true)) {
fm_set_msg('Unable to create destination folder', 'error');
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
}
// move?
$move = isset($_POST['move']);
// copy/move
$errors = 0;
$files = $_POST['file'];
if (is_array($files) && count($files)) {
foreach ($files as $f) {
if ($f != '') {
$f = fm_clean_path($f);
// abs path from
$from = $path . '/' . $f;
// abs path to
$dest = $copy_to_path . '/' . $f;
// do
if ($move) {
$rename = fm_rename($from, $dest);
if ($rename === false) {
$errors++;
}
} else {
if (!fm_rcopy($from, $dest)) {
$errors++;
}
}
}
}
if ($errors == 0) {
$msg = $move ? 'Selected files and folders moved' : 'Selected files and folders copied';
fm_set_msg($msg);
} else {
$msg = $move ? 'Error while moving items' : 'Error while copying items';
fm_set_msg($msg, 'error');
}
} else {
fm_set_msg(lng('Nothing selected'), 'alert');
}
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
// Rename
if (isset($_POST['rename_from'], $_POST['rename_to'], $_POST['token']) && !FM_READONLY) {
if(!verifyToken($_POST['token'])) {
fm_set_msg("Invalid Token.", 'error');
}
// old name
$old = urldecode($_POST['rename_from']);
$old = fm_clean_path($old);
$old = str_replace('/', '', $old);
// new name
$new = urldecode($_POST['rename_to']);
$new = fm_clean_path(strip_tags($new));
$new = str_replace('/', '', $new);
// path
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
$path .= '/' . FM_PATH;
}
// rename
if (fm_isvalid_filename($new) && $old != '' && $new != '') {
if (fm_rename($path . '/' . $old, $path . '/' . $new)) {
fm_set_msg(sprintf(lng('Renamed from').' %s '. lng('to').' %s', fm_enc($old), fm_enc($new)));
} else {
fm_set_msg(sprintf(lng('Error while renaming from').' %s '. lng('to').' %s', fm_enc($old), fm_enc($new)), 'error');
}
} else {
fm_set_msg(lng('Invalid characters in file name'), 'error');
}
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
// Download
if (isset($_GET['dl'], $_POST['token'])) {
if(!verifyToken($_POST['token'])) {
fm_set_msg("Invalid Token.", 'error');
}
$dl = urldecode($_GET['dl']);
$dl = fm_clean_path($dl);
$dl = str_replace('/', '', $dl);
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
$path .= '/' . FM_PATH;
}
if ($dl != '' && is_file($path . '/' . $dl)) {
fm_download_file($path . '/' . $dl, $dl, 1024);
exit;
} else {
fm_set_msg(lng('File not found'), 'error');
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
}
// Upload
if (!empty($_FILES) && !FM_READONLY) {
if(isset($_POST['token'])) {
if(!verifyToken($_POST['token'])) {
$response = array ('status' => 'error','info' => "Invalid Token.");
echo json_encode($response); exit();
}
} else {
$response = array ('status' => 'error','info' => "Token Missing.");
echo json_encode($response); exit();
}
$chunkIndex = $_POST['dzchunkindex'];
$chunkTotal = $_POST['dztotalchunkcount'];
$fullPathInput = fm_clean_path($_REQUEST['fullpath']);
$f = $_FILES;
$path = FM_ROOT_PATH;
$ds = DIRECTORY_SEPARATOR;
if (FM_PATH != '') {
$path .= '/' . FM_PATH;
}
$errors = 0;
$uploads = 0;
$allowed = (FM_UPLOAD_EXTENSION) ? explode(',', FM_UPLOAD_EXTENSION) : false;
$response = array (
'status' => 'error',
'info' => 'Oops! Try again'
);
$filename = $f['file']['name'];
$tmp_name = $f['file']['tmp_name'];
$ext = pathinfo($filename, PATHINFO_FILENAME) != '' ? strtolower(pathinfo($filename, PATHINFO_EXTENSION)) : '';
$isFileAllowed = ($allowed) ? in_array($ext, $allowed) : true;
if(!fm_isvalid_filename($filename) && !fm_isvalid_filename($fullPathInput)) {
$response = array (
'status' => 'error',
'info' => "Invalid File name!",
);
echo json_encode($response); exit();
}
$targetPath = $path . $ds;
if ( is_writable($targetPath) ) {
$fullPath = $path . '/' . $fullPathInput;
$folder = substr($fullPath, 0, strrpos($fullPath, "/"));
if (!is_dir($folder)) {
$old = umask(0);
mkdir($folder, 0777, true);
umask($old);
}
if (empty($f['file']['error']) && !empty($tmp_name) && $tmp_name != 'none' && $isFileAllowed) {
if ($chunkTotal){
$out = @fopen("{$fullPath}.part", $chunkIndex == 0 ? "wb" : "ab");
if ($out) {
$in = @fopen($tmp_name, "rb");
if ($in) {
if (PHP_VERSION_ID < 80009) {
// workaround https://bugs.php.net/bug.php?id=81145
do {
for (;;) {
$buff = fread($in, 4096);
if ($buff === false || $buff === '') {
break;
}
fwrite($out, $buff);
}
} while (!feof($in));
} else {
stream_copy_to_stream($in, $out);
}
$response = array (
'status' => 'success',
'info' => "file upload successful"
);
} else {
$response = array (
'status' => 'error',
'info' => "failed to open output stream",
'errorDetails' => error_get_last()
);
}
@fclose($in);
@fclose($out);
@unlink($tmp_name);
$response = array (
'status' => 'success',
'info' => "file upload successful"
);
} else {
$response = array (
'status' => 'error',
'info' => "failed to open output stream"
);
}
if ($chunkIndex == $chunkTotal - 1) {
if (file_exists ($fullPath)) {
$ext_1 = $ext ? '.'.$ext : '';
$fullPathTarget = $path . '/' . basename($fullPathInput, $ext_1) .'_'. date('ymdHis'). $ext_1;
} else {
$fullPathTarget = $fullPath;
}
rename("{$fullPath}.part", $fullPathTarget);
}
} else if (move_uploaded_file($tmp_name, $fullPath)) {
// Be sure that the file has been uploaded
if ( file_exists($fullPath) ) {
$response = array (
'status' => 'success',
'info' => "file upload successful"
);
} else {
$response = array (
'status' => 'error',
'info' => 'Couldn\'t upload the requested file.'
);
}
} else {
$response = array (
'status' => 'error',
'info' => "Error while uploading files. Uploaded files $uploads",
);
}
}
} else {
$response = array (
'status' => 'error',
'info' => 'The specified folder for upload isn\'t writeable.'
);
}
// Return the response
echo json_encode($response);
exit();
}
// Mass deleting
if (isset($_POST['group'], $_POST['delete'], $_POST['token']) && !FM_READONLY) {
if(!verifyToken($_POST['token'])) {
fm_set_msg(lng("Invalid Token."), 'error');
}
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
$path .= '/' . FM_PATH;
}
$errors = 0;
$files = $_POST['file'];
if (is_array($files) && count($files)) {
foreach ($files as $f) {
if ($f != '') {
$new_path = $path . '/' . $f;
if (!fm_rdelete($new_path)) {
$errors++;
}
}
}
if ($errors == 0) {
fm_set_msg(lng('Selected files and folder deleted'));
} else {
fm_set_msg(lng('Error while deleting items'), 'error');
}
} else {
fm_set_msg(lng('Nothing selected'), 'alert');
}
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
// Pack files zip, tar
if (isset($_POST['group'], $_POST['token']) && (isset($_POST['zip']) || isset($_POST['tar'])) && !FM_READONLY) {
if(!verifyToken($_POST['token'])) {
fm_set_msg(lng("Invalid Token."), 'error');
}
$path = FM_ROOT_PATH;
$ext = 'zip';
if (FM_PATH != '') {
$path .= '/' . FM_PATH;
}
//set pack type
$ext = isset($_POST['tar']) ? 'tar' : 'zip';
if (($ext == "zip" && !class_exists('ZipArchive')) || ($ext == "tar" && !class_exists('PharData'))) {
fm_set_msg(lng('Operations with archives are not available'), 'error');
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
$files = $_POST['file'];
$sanitized_files = array();
// clean path
foreach($files as $file){
array_push($sanitized_files, fm_clean_path($file));
}
$files = $sanitized_files;
if (!empty($files)) {
chdir($path);
if (count($files) == 1) {
$one_file = reset($files);
$one_file = basename($one_file);
$zipname = $one_file . '_' . date('ymd_His') . '.'.$ext;
} else {
$zipname = 'archive_' . date('ymd_His') . '.'.$ext;
}
if($ext == 'zip') {
$zipper = new FM_Zipper();
$res = $zipper->create($zipname, $files);
} elseif ($ext == 'tar') {
$tar = new FM_Zipper_Tar();
$res = $tar->create($zipname, $files);
}
if ($res) {
fm_set_msg(sprintf(lng('Archive').' %s '.lng('Created'), fm_enc($zipname)));
} else {
fm_set_msg(lng('Archive not created'), 'error');
}
} else {
fm_set_msg(lng('Nothing selected'), 'alert');
}
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
// Unpack zip, tar
if (isset($_POST['unzip'], $_POST['token']) && !FM_READONLY) {
if(!verifyToken($_POST['token'])) {
fm_set_msg(lng("Invalid Token."), 'error');
}
$unzip = urldecode($_POST['unzip']);
$unzip = fm_clean_path($unzip);
$unzip = str_replace('/', '', $unzip);
$isValid = false;
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
$path .= '/' . FM_PATH;
}
if ($unzip != '' && is_file($path . '/' . $unzip)) {
$zip_path = $path . '/' . $unzip;
$ext = pathinfo($zip_path, PATHINFO_EXTENSION);
$isValid = true;
} else {
fm_set_msg(lng('File not found'), 'error');
}
if (($ext == "zip" && !class_exists('ZipArchive')) || ($ext == "tar" && !class_exists('PharData'))) {
fm_set_msg(lng('Operations with archives are not available'), 'error');
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
if ($isValid) {
//to folder
$tofolder = '';
if (isset($_POST['tofolder'])) {
$tofolder = pathinfo($zip_path, PATHINFO_FILENAME);
if (fm_mkdir($path . '/' . $tofolder, true)) {
$path .= '/' . $tofolder;
}
}
if($ext == "zip") {
$zipper = new FM_Zipper();
$res = $zipper->unzip($zip_path, $path);
} elseif ($ext == "tar") {
try {
$gzipper = new PharData($zip_path);
if (@$gzipper->extractTo($path,null, true)) {
$res = true;
} else {
$res = false;
}
} catch (Exception $e) {
//TODO:: need to handle the error
$res = true;
}
}
if ($res) {
fm_set_msg(lng('Archive unpacked'));
} else {
fm_set_msg(lng('Archive not unpacked'), 'error');
}
} else {
fm_set_msg(lng('File not found'), 'error');
}
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
// Change Perms (not for Windows)
if (isset($_POST['chmod'], $_POST['token']) && !FM_READONLY && !FM_IS_WIN) {
if(!verifyToken($_POST['token'])) {
fm_set_msg(lng("Invalid Token."), 'error');
}
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
$path .= '/' . FM_PATH;
}
$file = $_POST['chmod'];
$file = fm_clean_path($file);
$file = str_replace('/', '', $file);
if ($file == '' || (!is_file($path . '/' . $file) && !is_dir($path . '/' . $file))) {
fm_set_msg(lng('File not found'), 'error');
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
$mode = 0;
if (!empty($_POST['ur'])) {
$mode |= 0400;
}
if (!empty($_POST['uw'])) {
$mode |= 0200;
}
if (!empty($_POST['ux'])) {
$mode |= 0100;
}
if (!empty($_POST['gr'])) {
$mode |= 0040;
}
if (!empty($_POST['gw'])) {
$mode |= 0020;
}
if (!empty($_POST['gx'])) {
$mode |= 0010;
}
if (!empty($_POST['or'])) {
$mode |= 0004;
}
if (!empty($_POST['ow'])) {
$mode |= 0002;
}
if (!empty($_POST['ox'])) {
$mode |= 0001;
}
if (@chmod($path . '/' . $file, $mode)) {
fm_set_msg(lng('Permissions changed'));
} else {
fm_set_msg(lng('Permissions not changed'), 'error');
}
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
}
/*************************** ACTIONS ***************************/
// get current path
$path = FM_ROOT_PATH;
if (FM_PATH != '') {
$path .= '/' . FM_PATH;
}
// check path
if (!is_dir($path)) {
fm_redirect(FM_SELF_URL . '?p=');
}
// get parent folder
$parent = fm_get_parent_path(FM_PATH);
$objects = is_readable($path) ? scandir($path) : array();
$folders = array();
$files = array();
$current_path = array_slice(explode("/",$path), -1)[0];
if (is_array($objects) && fm_is_exclude_items($current_path)) {
foreach ($objects as $file) {
if ($file == '.' || $file == '..') {
continue;
}
if (!FM_SHOW_HIDDEN && substr($file, 0, 1) === '.') {
continue;
}
$new_path = $path . '/' . $file;
if (@is_file($new_path) && fm_is_exclude_items($file)) {
$files[] = $file;
} elseif (@is_dir($new_path) && $file != '.' && $file != '..' && fm_is_exclude_items($file)) {
$folders[] = $file;
}
}
}
if (!empty($files)) {
natcasesort($files);
}
if (!empty($folders)) {
natcasesort($folders);
}
// upload form
if (isset($_GET['upload']) && !FM_READONLY) {
fm_show_header(); // HEADER
fm_show_nav_path(FM_PATH); // current path
//get the allowed file extensions
function getUploadExt() {
$extArr = explode(',', FM_UPLOAD_EXTENSION);
if(FM_UPLOAD_EXTENSION && $extArr) {
array_walk($extArr, function(&$x) {$x = ".$x";});
return implode(',', $extArr);
}
return '';
}
?>
""
:
File size:
MIME-type:
:
:
:
: %
'.lng('Image size').': ' . (isset($image_size[0]) ? $image_size[0] : '0') . ' x ' . (isset($image_size[1]) ? $image_size[1] : '0') . '
';
}
// Text info
if ($is_text) {
$is_utf8 = fm_is_utf8($content);
if (function_exists('iconv')) {
if (!$is_utf8) {
$content = iconv(FM_ICONV_INPUT_ENC, 'UTF-8//IGNORE', $content);
}
}
echo ''.lng('Charset').': ' . ($is_utf8 ? 'utf-8' : '8 bit') . '
';
}
?>
';
} else if($online_viewer == 'microsoft') {
echo '
';
}
} elseif ($is_zip) {
// ZIP content
if ($filenames !== false) {
echo '
';
foreach ($filenames as $fn) {
if ($fn['folder']) {
echo '' . fm_enc($fn['name']) . '
';
} else {
echo $fn['name'] . ' (' . fm_get_filesize($fn['filesize']) . ')
';
}
}
echo '
';
} else {
echo '
'.lng('Error while fetching archive info').'
';
}
} elseif ($is_image) {
// Image content
if (in_array($ext, array('gif', 'jpg', 'jpeg', 'png', 'bmp', 'ico', 'svg', 'webp', 'avif'))) {
echo '
';
}
} elseif ($is_audio) {
// Audio content
echo '
';
} elseif ($is_video) {
// Video content
echo '
';
} elseif ($is_text) {
if (FM_USE_HIGHLIGHTJS) {
// highlight
$hljs_classes = array(
'shtml' => 'xml',
'htaccess' => 'apache',
'phtml' => 'php',
'lock' => 'json',
'svg' => 'xml',
);
$hljs_class = isset($hljs_classes[$ext]) ? 'lang-' . $hljs_classes[$ext] : 'lang-' . $ext;
if (empty($ext) || in_array(strtolower($file), fm_get_text_names()) || preg_match('#\.min\.(css|js)$#i', $file)) {
$hljs_class = 'nohighlight';
}
$content = '
' . fm_enc($content) . '
';
} elseif (in_array($ext, array('php', 'php4', 'php5', 'phtml', 'phps'))) {
// php highlight
$content = highlight_string($content, true);
} else {
$content = '
' . fm_enc($content) . '
';
}
echo $content;
}
?>
'. $file. '';
header('X-XSS-Protection:0');
fm_show_header(); // HEADER
fm_show_nav_path(FM_PATH); // current path
$file_url = FM_ROOT_URL . fm_convert_win((FM_PATH != '' ? '/' . FM_PATH : '') . '/' . $file);
$file_path = $path . '/' . $file;
// normal editer
$isNormalEditor = true;
if (isset($_GET['env'])) {
if ($_GET['env'] == "ace") {
$isNormalEditor = false;
}
}
// Save File
if (isset($_POST['savedata'])) {
$writedata = $_POST['savedata'];
$fd = fopen($file_path, "w");
@fwrite($fd, $writedata);
fclose($fd);
fm_set_msg(lng('File Saved Successfully'));
}
$ext = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
$mime_type = fm_get_mime_type($file_path);
$filesize = filesize($file_path);
$is_text = false;
$content = ''; // for text
if (in_array($ext, fm_get_text_exts()) || substr($mime_type, 0, 4) == 'text' || in_array($mime_type, fm_get_text_mimes())) {
$is_text = true;
$content = file_get_contents($file_path);
}
?>
' . htmlspecialchars($content) . '';
echo '';
} elseif ($is_text) {
echo '
' . htmlspecialchars($content) . '
';
} else {
fm_set_msg(lng('FILE EXTENSION HAS NOT SUPPORTED'), 'error');
}
?>
";
return;
}
echo "$external[$key]";
}
/**
* Verify CSRF TOKEN and remove after cerify
* @param string $token
* @return bool
*/
function verifyToken($token)
{
if (hash_equals($_SESSION['token'], $token)) {
return true;
}
return false;
}
/**
* Delete file or folder (recursively)
* @param string $path
* @return bool
*/
function fm_rdelete($path)
{
if (is_link($path)) {
return unlink($path);
} elseif (is_dir($path)) {
$objects = scandir($path);
$ok = true;
if (is_array($objects)) {
foreach ($objects as $file) {
if ($file != '.' && $file != '..') {
if (!fm_rdelete($path . '/' . $file)) {
$ok = false;
}
}
}
}
return ($ok) ? rmdir($path) : false;
} elseif (is_file($path)) {
return unlink($path);
}
return false;
}
/**
* Recursive chmod
* @param string $path
* @param int $filemode
* @param int $dirmode
* @return bool
* @todo Will use in mass chmod
*/
function fm_rchmod($path, $filemode, $dirmode)
{
if (is_dir($path)) {
if (!chmod($path, $dirmode)) {
return false;
}
$objects = scandir($path);
if (is_array($objects)) {
foreach ($objects as $file) {
if ($file != '.' && $file != '..') {
if (!fm_rchmod($path . '/' . $file, $filemode, $dirmode)) {
return false;
}
}
}
}
return true;
} elseif (is_link($path)) {
return true;
} elseif (is_file($path)) {
return chmod($path, $filemode);
}
return false;
}
/**
* Check the file extension which is allowed or not
* @param string $filename
* @return bool
*/
function fm_is_valid_ext($filename)
{
$allowed = (FM_FILE_EXTENSION) ? explode(',', FM_FILE_EXTENSION) : false;
$ext = pathinfo($filename, PATHINFO_EXTENSION);
$isFileAllowed = ($allowed) ? in_array($ext, $allowed) : true;
return ($isFileAllowed) ? true : false;
}
/**
* Safely rename
* @param string $old
* @param string $new
* @return bool|null
*/
function fm_rename($old, $new)
{
$isFileAllowed = fm_is_valid_ext($new);
if(!is_dir($old)) {
if (!$isFileAllowed) return false;
}
return (!file_exists($new) && file_exists($old)) ? rename($old, $new) : null;
}
/**
* Copy file or folder (recursively).
* @param string $path
* @param string $dest
* @param bool $upd Update files
* @param bool $force Create folder with same names instead file
* @return bool
*/
function fm_rcopy($path, $dest, $upd = true, $force = true)
{
if (is_dir($path)) {
if (!fm_mkdir($dest, $force)) {
return false;
}
$objects = scandir($path);
$ok = true;
if (is_array($objects)) {
foreach ($objects as $file) {
if ($file != '.' && $file != '..') {
if (!fm_rcopy($path . '/' . $file, $dest . '/' . $file)) {
$ok = false;
}
}
}
}
return $ok;
} elseif (is_file($path)) {
return fm_copy($path, $dest, $upd);
}
return false;
}
/**
* Safely create folder
* @param string $dir
* @param bool $force
* @return bool
*/
function fm_mkdir($dir, $force)
{
if (file_exists($dir)) {
if (is_dir($dir)) {
return $dir;
} elseif (!$force) {
return false;
}
unlink($dir);
}
return mkdir($dir, 0777, true);
}
/**
* Safely copy file
* @param string $f1
* @param string $f2
* @param bool $upd Indicates if file should be updated with new content
* @return bool
*/
function fm_copy($f1, $f2, $upd)
{
$time1 = filemtime($f1);
if (file_exists($f2)) {
$time2 = filemtime($f2);
if ($time2 >= $time1 && $upd) {
return false;
}
}
$ok = copy($f1, $f2);
if ($ok) {
touch($f2, $time1);
}
return $ok;
}
/**
* Get mime type
* @param string $file_path
* @return mixed|string
*/
function fm_get_mime_type($file_path)
{
if (function_exists('finfo_open')) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file_path);
finfo_close($finfo);
return $mime;
} elseif (function_exists('mime_content_type')) {
return mime_content_type($file_path);
} elseif (!stristr(ini_get('disable_functions'), 'shell_exec')) {
$file = escapeshellarg($file_path);
$mime = shell_exec('file -bi ' . $file);
return $mime;
} else {
return '--';
}
}
/**
* HTTP Redirect
* @param string $url
* @param int $code
*/
function fm_redirect($url, $code = 302)
{
header('Location: ' . $url, true, $code);
exit;
}
/**
* Path traversal prevention and clean the url
* It replaces (consecutive) occurrences of / and \\ with whatever is in DIRECTORY_SEPARATOR, and processes /. and /.. fine.
* @param $path
* @return string
*/
function get_absolute_path($path) {
$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
$parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen');
$absolutes = array();
foreach ($parts as $part) {
if ('.' == $part) continue;
if ('..' == $part) {
array_pop($absolutes);
} else {
$absolutes[] = $part;
}
}
return implode(DIRECTORY_SEPARATOR, $absolutes);
}
/**
* Clean path
* @param string $path
* @return string
*/
function fm_clean_path($path, $trim = true)
{
$path = $trim ? trim($path) : $path;
$path = trim($path, '\\/');
$path = str_replace(array('../', '..\\'), '', $path);
$path = get_absolute_path($path);
if ($path == '..') {
$path = '';
}
return str_replace('\\', '/', $path);
}
/**
* Get parent path
* @param string $path
* @return bool|string
*/
function fm_get_parent_path($path)
{
$path = fm_clean_path($path);
if ($path != '') {
$array = explode('/', $path);
if (count($array) > 1) {
$array = array_slice($array, 0, -1);
return implode('/', $array);
}
return '';
}
return false;
}
function fm_get_display_path($file_path)
{
global $path_display_mode, $root_path, $root_url;
switch ($path_display_mode) {
case 'relative':
return array(
'label' => 'Path',
'path' => fm_enc(fm_convert_win(str_replace($root_path, '', $file_path)))
);
case 'host':
$relative_path = str_replace($root_path, '', $file_path);
return array(
'label' => 'Host Path',
'path' => fm_enc(fm_convert_win('/' . $root_url . '/' . ltrim(str_replace('\\', '/', $relative_path), '/')))
);
case 'full':
default:
return array(
'label' => 'Full Path',
'path' => fm_enc(fm_convert_win($file_path))
);
}
}
/**
* Check file is in exclude list
* @param string $file
* @return bool
*/
function fm_is_exclude_items($file) {
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (isset($exclude_items) and sizeof($exclude_items)) {
unset($exclude_items);
}
$exclude_items = FM_EXCLUDE_ITEMS;
if (version_compare(PHP_VERSION, '7.0.0', '<')) {
$exclude_items = unserialize($exclude_items);
}
if (!in_array($file, $exclude_items) && !in_array("*.$ext", $exclude_items)) {
return true;
}
return false;
}
/**
* get language translations from json file
* @param int $tr
* @return array
*/
function fm_get_translations($tr) {
try {
$content = @file_get_contents('translation.json');
if($content !== FALSE) {
$lng = json_decode($content, TRUE);
global $lang_list;
foreach ($lng["language"] as $key => $value)
{
$code = $value["code"];
$lang_list[$code] = $value["name"];
if ($tr)
$tr[$code] = $value["translation"];
}
return $tr;
}
}
catch (Exception $e) {
echo $e;
}
}
/**
* @param string $file
* Recover all file sizes larger than > 2GB.
* Works on php 32bits and 64bits and supports linux
* @return int|string
*/
function fm_get_size($file)
{
static $iswin;
static $isdarwin;
if (!isset($iswin)) {
$iswin = (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN');
}
if (!isset($isdarwin)) {
$isdarwin = (strtoupper(substr(PHP_OS, 0)) == "DARWIN");
}
static $exec_works;
if (!isset($exec_works)) {
$exec_works = (function_exists('exec') && !ini_get('safe_mode') && @exec('echo EXEC') == 'EXEC');
}
// try a shell command
if ($exec_works) {
$arg = escapeshellarg($file);
$cmd = ($iswin) ? "for %F in (\"$file\") do @echo %~zF" : ($isdarwin ? "stat -f%z $arg" : "stat -c%s $arg");
@exec($cmd, $output);
if (is_array($output) && ctype_digit($size = trim(implode("\n", $output)))) {
return $size;
}
}
// try the Windows COM interface
if ($iswin && class_exists("COM")) {
try {
$fsobj = new COM('Scripting.FileSystemObject');
$f = $fsobj->GetFile( realpath($file) );
$size = $f->Size;
} catch (Exception $e) {
$size = null;
}
if (ctype_digit($size)) {
return $size;
}
}
// if all else fails
return filesize($file);
}
/**
* Get nice filesize
* @param int $size
* @return string
*/
function fm_get_filesize($size)
{
$size = (float) $size;
$units = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
$power = ($size > 0) ? floor(log($size, 1024)) : 0;
$power = ($power > (count($units) - 1)) ? (count($units) - 1) : $power;
return sprintf('%s %s', round($size / pow(1024, $power), 2), $units[$power]);
}
/**
* Get total size of directory tree.
*
* @param string $directory Relative or absolute directory name.
* @return int Total number of bytes.
*/
function fm_get_directorysize($directory) {
$bytes = 0;
$directory = realpath($directory);
if ($directory !== false && $directory != '' && file_exists($directory)){
foreach(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS)) as $file){
$bytes += $file->getSize();
}
}
return $bytes;
}
/**
* Get info about zip archive
* @param string $path
* @return array|bool
*/
function fm_get_zif_info($path, $ext) {
if ($ext == 'zip' && function_exists('zip_open')) {
$arch = @zip_open($path);
if ($arch) {
$filenames = array();
while ($zip_entry = @zip_read($arch)) {
$zip_name = @zip_entry_name($zip_entry);
$zip_folder = substr($zip_name, -1) == '/';
$filenames[] = array(
'name' => $zip_name,
'filesize' => @zip_entry_filesize($zip_entry),
'compressed_size' => @zip_entry_compressedsize($zip_entry),
'folder' => $zip_folder
//'compression_method' => zip_entry_compressionmethod($zip_entry),
);
}
@zip_close($arch);
return $filenames;
}
} elseif($ext == 'tar' && class_exists('PharData')) {
$archive = new PharData($path);
$filenames = array();
foreach(new RecursiveIteratorIterator($archive) as $file) {
$parent_info = $file->getPathInfo();
$zip_name = str_replace("phar://".$path, '', $file->getPathName());
$zip_name = substr($zip_name, ($pos = strpos($zip_name, '/')) !== false ? $pos + 1 : 0);
$zip_folder = $parent_info->getFileName();
$zip_info = new SplFileInfo($file);
$filenames[] = array(
'name' => $zip_name,
'filesize' => $zip_info->getSize(),
'compressed_size' => $file->getCompressedSize(),
'folder' => $zip_folder
);
}
return $filenames;
}
return false;
}
/**
* Encode html entities
* @param string $text
* @return string
*/
function fm_enc($text)
{
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}
/**
* Prevent XSS attacks
* @param string $text
* @return string
*/
function fm_isvalid_filename($text) {
return (strpbrk($text, '/?%*:|"<>') === FALSE) ? true : false;
}
/**
* Save message in session
* @param string $msg
* @param string $status
*/
function fm_set_msg($msg, $status = 'ok')
{
$_SESSION[FM_SESSION_ID]['message'] = $msg;
$_SESSION[FM_SESSION_ID]['status'] = $status;
}
/**
* Check if string is in UTF-8
* @param string $string
* @return int
*/
function fm_is_utf8($string)
{
return preg_match('//u', $string);
}
/**
* Convert file name to UTF-8 in Windows
* @param string $filename
* @return string
*/
function fm_convert_win($filename)
{
if (FM_IS_WIN && function_exists('iconv')) {
$filename = iconv(FM_ICONV_INPUT_ENC, 'UTF-8//IGNORE', $filename);
}
return $filename;
}
/**
* @param $obj
* @return array
*/
function fm_object_to_array($obj)
{
if (!is_object($obj) && !is_array($obj)) {
return $obj;
}
if (is_object($obj)) {
$obj = get_object_vars($obj);
}
return array_map('fm_object_to_array', $obj);
}
/**
* Get CSS classname for file
* @param string $path
* @return string
*/
function fm_get_file_icon_class($path)
{
// get extension
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
switch ($ext) {
case 'ico':
case 'gif':
case 'jpg':
case 'jpeg':
case 'jpc':
case 'jp2':
case 'jpx':
case 'xbm':
case 'wbmp':
case 'png':
case 'bmp':
case 'tif':
case 'tiff':
case 'webp':
case 'avif':
case 'svg':
$img = 'fa fa-picture-o';
break;
case 'passwd':
case 'ftpquota':
case 'sql':
case 'js':
case 'ts':
case 'jsx':
case 'tsx':
case 'hbs':
case 'json':
case 'sh':
case 'config':
case 'twig':
case 'tpl':
case 'md':
case 'gitignore':
case 'c':
case 'cpp':
case 'cs':
case 'py':
case 'rs':
case 'map':
case 'lock':
case 'dtd':
$img = 'fa fa-file-code-o';
break;
case 'txt':
case 'ini':
case 'conf':
case 'log':
case 'htaccess':
case 'yaml':
case 'yml':
case 'toml':
case 'tmp':
case 'top':
case 'bot':
case 'dat':
case 'bak':
case 'htpasswd':
case 'pl':
$img = 'fa fa-file-text-o';
break;
case 'css':
case 'less':
case 'sass':
case 'scss':
$img = 'fa fa-css3';
break;
case 'bz2':
case 'tbz2':
case 'tbz':
case 'zip':
case 'rar':
case 'gz':
case 'tgz':
case 'tar':
case '7z':
case 'xz':
case 'txz':
case 'zst':
case 'tzst':
$img = 'fa fa-file-archive-o';
break;
case 'php':
case 'php4':
case 'php5':
case 'phps':
case 'phtml':
$img = 'fa fa-code';
break;
case 'htm':
case 'html':
case 'shtml':
case 'xhtml':
$img = 'fa fa-html5';
break;
case 'xml':
case 'xsl':
$img = 'fa fa-file-excel-o';
break;
case 'wav':
case 'mp3':
case 'mp2':
case 'm4a':
case 'aac':
case 'ogg':
case 'oga':
case 'wma':
case 'mka':
case 'flac':
case 'ac3':
case 'tds':
$img = 'fa fa-music';
break;
case 'm3u':
case 'm3u8':
case 'pls':
case 'cue':
case 'xspf':
$img = 'fa fa-headphones';
break;
case 'avi':
case 'mpg':
case 'mpeg':
case 'mp4':
case 'm4v':
case 'flv':
case 'f4v':
case 'ogm':
case 'ogv':
case 'mov':
case 'mkv':
case '3gp':
case 'asf':
case 'wmv':
case 'webm':
$img = 'fa fa-file-video-o';
break;
case 'eml':
case 'msg':
$img = 'fa fa-envelope-o';
break;
case 'xls':
case 'xlsx':
case 'ods':
$img = 'fa fa-file-excel-o';
break;
case 'csv':
$img = 'fa fa-file-text-o';
break;
case 'bak':
case 'swp':
$img = 'fa fa-clipboard';
break;
case 'doc':
case 'docx':
case 'odt':
$img = 'fa fa-file-word-o';
break;
case 'ppt':
case 'pptx':
$img = 'fa fa-file-powerpoint-o';
break;
case 'ttf':
case 'ttc':
case 'otf':
case 'woff':
case 'woff2':
case 'eot':
case 'fon':
$img = 'fa fa-font';
break;
case 'pdf':
$img = 'fa fa-file-pdf-o';
break;
case 'psd':
case 'ai':
case 'eps':
case 'fla':
case 'swf':
$img = 'fa fa-file-image-o';
break;
case 'exe':
case 'msi':
$img = 'fa fa-file-o';
break;
case 'bat':
$img = 'fa fa-terminal';
break;
default:
$img = 'fa fa-info-circle';
}
return $img;
}
/**
* Get image files extensions
* @return array
*/
function fm_get_image_exts()
{
return array('ico', 'gif', 'jpg', 'jpeg', 'jpc', 'jp2', 'jpx', 'xbm', 'wbmp', 'png', 'bmp', 'tif', 'tiff', 'psd', 'svg', 'webp', 'avif');
}
/**
* Get video files extensions
* @return array
*/
function fm_get_video_exts()
{
return array('avi', 'webm', 'wmv', 'mp4', 'm4v', 'ogm', 'ogv', 'mov', 'mkv');
}
/**
* Get audio files extensions
* @return array
*/
function fm_get_audio_exts()
{
return array('wav', 'mp3', 'ogg', 'm4a');
}
/**
* Get text file extensions
* @return array
*/
function fm_get_text_exts()
{
return array(
'txt', 'css', 'ini', 'conf', 'log', 'htaccess', 'passwd', 'ftpquota', 'sql', 'js', 'ts', 'jsx', 'tsx', 'mjs', 'json', 'sh', 'config',
'php', 'php4', 'php5', 'phps', 'phtml', 'htm', 'html', 'shtml', 'xhtml', 'xml', 'xsl', 'm3u', 'm3u8', 'pls', 'cue', 'bash', 'vue',
'eml', 'msg', 'csv', 'bat', 'twig', 'tpl', 'md', 'gitignore', 'less', 'sass', 'scss', 'c', 'cpp', 'cs', 'py', 'go', 'zsh', 'swift',
'map', 'lock', 'dtd', 'svg', 'asp', 'aspx', 'asx', 'asmx', 'ashx', 'jsp', 'jspx', 'cgi', 'dockerfile', 'ruby', 'yml', 'yaml', 'toml',
'vhost', 'scpt', 'applescript', 'csx', 'cshtml', 'c++', 'coffee', 'cfm', 'rb', 'graphql', 'mustache', 'jinja', 'http', 'handlebars',
'java', 'es', 'es6', 'markdown', 'wiki', 'tmp', 'top', 'bot', 'dat', 'bak', 'htpasswd', 'pl'
);
}
/**
* Get mime types of text files
* @return array
*/
function fm_get_text_mimes()
{
return array(
'application/xml',
'application/javascript',
'application/x-javascript',
'image/svg+xml',
'message/rfc822',
'application/json',
);
}
/**
* Get file names of text files w/o extensions
* @return array
*/
function fm_get_text_names()
{
return array(
'license',
'readme',
'authors',
'contributors',
'changelog',
);
}
/**
* Get online docs viewer supported files extensions
* @return array
*/
function fm_get_onlineViewer_exts()
{
return array('doc', 'docx', 'xls', 'xlsx', 'pdf', 'ppt', 'pptx', 'ai', 'psd', 'dxf', 'xps', 'rar', 'odt', 'ods');
}
/**
* It returns the mime type of a file based on its extension.
* @param extension The file extension of the file you want to get the mime type for.
* @return string|string[] The mime type of the file.
*/
function fm_get_file_mimes($extension)
{
$fileTypes['swf'] = 'application/x-shockwave-flash';
$fileTypes['pdf'] = 'application/pdf';
$fileTypes['exe'] = 'application/octet-stream';
$fileTypes['zip'] = 'application/zip';
$fileTypes['doc'] = 'application/msword';
$fileTypes['xls'] = 'application/vnd.ms-excel';
$fileTypes['ppt'] = 'application/vnd.ms-powerpoint';
$fileTypes['gif'] = 'image/gif';
$fileTypes['png'] = 'image/png';
$fileTypes['jpeg'] = 'image/jpg';
$fileTypes['jpg'] = 'image/jpg';
$fileTypes['webp'] = 'image/webp';
$fileTypes['avif'] = 'image/avif';
$fileTypes['rar'] = 'application/rar';
$fileTypes['ra'] = 'audio/x-pn-realaudio';
$fileTypes['ram'] = 'audio/x-pn-realaudio';
$fileTypes['ogg'] = 'audio/x-pn-realaudio';
$fileTypes['wav'] = 'video/x-msvideo';
$fileTypes['wmv'] = 'video/x-msvideo';
$fileTypes['avi'] = 'video/x-msvideo';
$fileTypes['asf'] = 'video/x-msvideo';
$fileTypes['divx'] = 'video/x-msvideo';
$fileTypes['mp3'] = 'audio/mpeg';
$fileTypes['mp4'] = 'audio/mpeg';
$fileTypes['mpeg'] = 'video/mpeg';
$fileTypes['mpg'] = 'video/mpeg';
$fileTypes['mpe'] = 'video/mpeg';
$fileTypes['mov'] = 'video/quicktime';
$fileTypes['swf'] = 'video/quicktime';
$fileTypes['3gp'] = 'video/quicktime';
$fileTypes['m4a'] = 'video/quicktime';
$fileTypes['aac'] = 'video/quicktime';
$fileTypes['m3u'] = 'video/quicktime';
$fileTypes['php'] = ['application/x-php'];
$fileTypes['html'] = ['text/html'];
$fileTypes['txt'] = ['text/plain'];
//Unknown mime-types should be 'application/octet-stream'
if(empty($fileTypes[$extension])) {
$fileTypes[$extension] = ['application/octet-stream'];
}
return $fileTypes[$extension];
}
/**
* This function scans the files and folder recursively, and return matching files
* @param string $dir
* @param string $filter
* @return array|null
*/
function scan($dir = '', $filter = '') {
$path = FM_ROOT_PATH.'/'.$dir;
if($path) {
$ite = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
$rii = new RegexIterator($ite, "/(" . $filter . ")/i");
$files = array();
foreach ($rii as $file) {
if (!$file->isDir()) {
$fileName = $file->getFilename();
$location = str_replace(FM_ROOT_PATH, '', $file->getPath());
$files[] = array(
"name" => $fileName,
"type" => "file",
"path" => $location,
);
}
}
return $files;
}
}
/**
* Parameters: downloadFile(File Location, File Name,
* max speed, is streaming
* If streaming - videos will show as videos, images as images
* instead of download prompt
* https://stackoverflow.com/a/13821992/1164642
*/
function fm_download_file($fileLocation, $fileName, $chunkSize = 1024)
{
if (connection_status() != 0)
return (false);
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
$contentType = fm_get_file_mimes($extension);
if(is_array($contentType)) {
$contentType = implode(' ', $contentType);
}
$size = filesize($fileLocation);
if ($size == 0) {
fm_set_msg(lng('Zero byte file! Aborting download'), 'error');
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
return (false);
}
@ini_set('magic_quotes_runtime', 0);
$fp = fopen("$fileLocation", "rb");
if ($fp === false) {
fm_set_msg(lng('Cannot open file! Aborting download'), 'error');
$FM_PATH=FM_PATH; fm_redirect(FM_SELF_URL . '?p=' . urlencode($FM_PATH));
return (false);
}
// headers
header('Content-Description: File Transfer');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header("Content-Transfer-Encoding: binary");
header("Content-Type: $contentType");
$contentDisposition = 'attachment';
if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE")) {
$fileName = preg_replace('/\./', '%2e', $fileName, substr_count($fileName, '.') - 1);
header("Content-Disposition: $contentDisposition;filename=\"$fileName\"");
} else {
header("Content-Disposition: $contentDisposition;filename=\"$fileName\"");
}
header("Accept-Ranges: bytes");
$range = 0;
if (isset($_SERVER['HTTP_RANGE'])) {
list($a, $range) = explode("=", $_SERVER['HTTP_RANGE']);
str_replace($range, "-", $range);
$size2 = $size - 1;
$new_length = $size - $range;
header("HTTP/1.1 206 Partial Content");
header("Content-Length: $new_length");
header("Content-Range: bytes $range$size2/$size");
} else {
$size2 = $size - 1;
header("Content-Range: bytes 0-$size2/$size");
header("Content-Length: " . $size);
}
$fileLocation = realpath($fileLocation);
while (ob_get_level()) ob_end_clean();
readfile($fileLocation);
fclose($fp);
return ((connection_status() == 0) and !connection_aborted());
}
/**
* If the theme is dark, return the text-white and bg-dark classes.
* @return string the value of the variable.
*/
function fm_get_theme() {
$result = '';
if(FM_THEME == "dark") {
$result = "text-white bg-dark";
}
return $result;
}
/**
* Class to work with zip files (using ZipArchive)
*/
class FM_Zipper
{
private $zip;
public function __construct()
{
$this->zip = new ZipArchive();
}
/**
* Create archive with name $filename and files $files (RELATIVE PATHS!)
* @param string $filename
* @param array|string $files
* @return bool
*/
public function create($filename, $files)
{
$res = $this->zip->open($filename, ZipArchive::CREATE);
if ($res !== true) {
return false;
}
if (is_array($files)) {
foreach ($files as $f) {
$f = fm_clean_path($f);
if (!$this->addFileOrDir($f)) {
$this->zip->close();
return false;
}
}
$this->zip->close();
return true;
} else {
if ($this->addFileOrDir($files)) {
$this->zip->close();
return true;
}
return false;
}
}
/**
* Extract archive $filename to folder $path (RELATIVE OR ABSOLUTE PATHS)
* @param string $filename
* @param string $path
* @return bool
*/
public function unzip($filename, $path)
{
$res = $this->zip->open($filename);
if ($res !== true) {
return false;
}
if ($this->zip->extractTo($path)) {
$this->zip->close();
return true;
}
return false;
}
/**
* Add file/folder to archive
* @param string $filename
* @return bool
*/
private function addFileOrDir($filename)
{
if (is_file($filename)) {
return $this->zip->addFile($filename);
} elseif (is_dir($filename)) {
return $this->addDir($filename);
}
return false;
}
/**
* Add folder recursively
* @param string $path
* @return bool
*/
private function addDir($path)
{
if (!$this->zip->addEmptyDir($path)) {
return false;
}
$objects = scandir($path);
if (is_array($objects)) {
foreach ($objects as $file) {
if ($file != '.' && $file != '..') {
if (is_dir($path . '/' . $file)) {
if (!$this->addDir($path . '/' . $file)) {
return false;
}
} elseif (is_file($path . '/' . $file)) {
if (!$this->zip->addFile($path . '/' . $file)) {
return false;
}
}
}
}
return true;
}
return false;
}
}
/**
* Class to work with Tar files (using PharData)
*/
class FM_Zipper_Tar
{
private $tar;
public function __construct()
{
$this->tar = null;
}
/**
* Create archive with name $filename and files $files (RELATIVE PATHS!)
* @param string $filename
* @param array|string $files
* @return bool
*/
public function create($filename, $files)
{
$this->tar = new PharData($filename);
if (is_array($files)) {
foreach ($files as $f) {
$f = fm_clean_path($f);
if (!$this->addFileOrDir($f)) {
return false;
}
}
return true;
} else {
if ($this->addFileOrDir($files)) {
return true;
}
return false;
}
}
/**
* Extract archive $filename to folder $path (RELATIVE OR ABSOLUTE PATHS)
* @param string $filename
* @param string $path
* @return bool
*/
public function unzip($filename, $path)
{
$res = $this->tar->open($filename);
if ($res !== true) {
return false;
}
if ($this->tar->extractTo($path)) {
return true;
}
return false;
}
/**
* Add file/folder to archive
* @param string $filename
* @return bool
*/
private function addFileOrDir($filename)
{
if (is_file($filename)) {
try {
$this->tar->addFile($filename);
return true;
} catch (Exception $e) {
return false;
}
} elseif (is_dir($filename)) {
return $this->addDir($filename);
}
return false;
}
/**
* Add folder recursively
* @param string $path
* @return bool
*/
private function addDir($path)
{
$objects = scandir($path);
if (is_array($objects)) {
foreach ($objects as $file) {
if ($file != '.' && $file != '..') {
if (is_dir($path . '/' . $file)) {
if (!$this->addDir($path . '/' . $file)) {
return false;
}
} elseif (is_file($path . '/' . $file)) {
try {
$this->tar->addFile($path . '/' . $file);
} catch (Exception $e) {
return false;
}
}
}
}
return true;
}
return false;
}
}
/**
* Save Configuration
*/
class FM_Config
{
var $data;
function __construct()
{
global $root_path, $root_url, $CONFIG;
$fm_url = $root_url.$_SERVER["PHP_SELF"];
$this->data = array(
'lang' => 'en',
'error_reporting' => true,
'show_hidden' => true
);
$data = false;
if (strlen($CONFIG)) {
$data = fm_object_to_array(json_decode($CONFIG));
} else {
$msg = 'Tiny File Manager
Error: Cannot load configuration';
if (substr($fm_url, -1) == '/') {
$fm_url = rtrim($fm_url, '/');
$msg .= '
';
$msg .= '
Seems like you have a trailing slash on the URL.';
$msg .= '
Try this link: ' . $fm_url . '';
}
die($msg);
}
if (is_array($data) && count($data)) $this->data = $data;
else $this->save();
}
function save()
{
$fm_file = __FILE__;
$var_name = '$CONFIG';
$var_value = var_export(json_encode($this->data), true);
$config_string = "
' . $_SESSION[FM_SESSION_ID]['message'] . '
';
unset($_SESSION[FM_SESSION_ID]['message']);
unset($_SESSION[FM_SESSION_ID]['status']);
}
}
/**
* Show page header in Login Form
*/
function fm_show_header_login()
{
$sprites_ver = '20160315';
header("Content-Type: text/html; charset=utf-8");
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
header("Pragma: no-cache");
global $lang, $root_url, $favicon_path;
?>
'; } ?>
">
'; } ?>
">