%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/bitrix/www/bitrix/modules/clouds/classes/general/
Upload File :
Create Path :
Current File : /home/bitrix/www/bitrix/modules/clouds/classes/general/storage_service_s3.php

<?
IncludeModuleLangFile(__FILE__);

class CCloudStorageService_AmazonS3 extends CCloudStorageService
{
	protected $status = 0;
	protected $verb = '';
	protected $host = '';
	protected $url = '';
	protected $headers =/*.(array[string]string).*/array();
	protected $set_headers =/*.(array[string]string).*/array();
	protected $errno = 0;
	protected $errstr = '';
	protected $result = '';
	protected $new_end_point = '';
	protected $_public = true;
	protected $location = '';
	/**
	 * @return int
	*/
	function GetLastRequestStatus()
	{
		return $this->status;
	}
	/**
	 * @return CCloudStorageService
	*/
	function GetObject()
	{
		return new CCloudStorageService_AmazonS3();
	}
	/**
	 * @return string
	*/
	function GetID()
	{
		return "amazon_s3";
	}
	/**
	 * @return string
	*/
	function GetName()
	{
		return "Amazon Simple Storage Service";
	}
	/**
	 * @return array[string]string
	*/
	function GetLocationList()
	{
		// http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
		return array(
			"" => "US Standard",
			"us-east-2" => "US East (Ohio)",
			"us-west-1" => "US West (Northern California)",
			"us-west-2" => "US West (Oregon)",
			"ca-central-1" => "Canada (Central)",
			"ap-south-1" => "Asia Pacific (Mumbai)",
			"ap-northeast-2" => "Asia Pacific (Seoul)",
			"ap-southeast-1" => "Asia Pacific (Singapore)",
			"ap-southeast-2" => "Asia Pacific (Sydney)",
			"ap-northeast-1" => "Asia Pacific (Tokyo)",
			"eu-central-1" => "EU (Frankfurt)",
			"eu-west-1" => "EU (Ireland)",
			"eu-west-2" => "EU (London)",
			"sa-east-1" => "South America (Sao Paulo)",
		);
	}
	/**
	 * @return array[string]string
	*/
	function GetAPList()
	{
		// http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
		return array(
			"" => "s3.amazonaws.com",
			"us-east-2" => "s3.us-east-2.amazonaws.com",
			"us-west-1" => "s3-us-west-1.amazonaws.com",
			"us-west-2" => "s3-us-west-2.amazonaws.com",
			"ca-central-1" => "s3.ca-central-1.amazonaws.com",
			"ap-south-1" => "s3.ap-south-1.amazonaws.com",
			"ap-northeast-2" => "s3.ap-northeast-2.amazonaws.com",
			"ap-southeast-1" => "s3-ap-southeast-1.amazonaws.com",
			"ap-southeast-2" => "s3-ap-southeast-2.amazonaws.com",
			"ap-northeast-1" => "s3-ap-northeast-1.amazonaws.com",
			"eu-central-1" => "s3.eu-central-1.amazonaws.com",
			"eu-west-1" => "s3-eu-west-1.amazonaws.com",
			"eu-west-2" => "s3.eu-west-2.amazonaws.com",
			"sa-east-1" => "South America (Sao Paulo)",
		);
	}
	/**
	 * @param array[string]string $arBucket
	 * @param bool $bServiceSet
	 * @param string $cur_SERVICE_ID
	 * @param bool $bVarsFromForm
	 * @return string
	*/
	function GetSettingsHTML($arBucket, $bServiceSet, $cur_SERVICE_ID, $bVarsFromForm)
	{
		if($bVarsFromForm)
			$arSettings = $_POST["SETTINGS"][$this->GetID()];
		else
			$arSettings = unserialize($arBucket["SETTINGS"]);

		if(!is_array($arSettings))
			$arSettings = array("ACCESS_KEY" => "", "SECRET_KEY" => "");

		$htmlID = htmlspecialcharsbx($this->GetID());

		$result = '
		<tr id="SETTINGS_0_'.$htmlID.'" style="display:'.($cur_SERVICE_ID === $this->GetID() || !$bServiceSet? '': 'none').'" class="settings-tr adm-detail-required-field">
			<td>'.GetMessage("CLO_STORAGE_S3_EDIT_ACCESS_KEY").':</td>
			<td><input type="hidden" name="SETTINGS['.$htmlID.'][ACCESS_KEY]" id="'.$htmlID.'ACCESS_KEY" value="'.htmlspecialcharsbx($arSettings['ACCESS_KEY']).'"><input type="text" size="55" name="'.$htmlID.'INP_ACCESS_KEY" id="'.$htmlID.'INP_ACCESS_KEY" value="'.htmlspecialcharsbx($arSettings['ACCESS_KEY']).'" '.($arBucket['READ_ONLY'] === 'Y'? '"disabled"': '').' onchange="BX(\''.$htmlID.'ACCESS_KEY\').value = this.value"></td>
		</tr>
		<tr id="SETTINGS_1_'.$htmlID.'" style="display:'.($cur_SERVICE_ID === $this->GetID() || !$bServiceSet? '': 'none').'" class="settings-tr adm-detail-required-field">
			<td>'.GetMessage("CLO_STORAGE_S3_EDIT_SECRET_KEY").':</td>
			<td><input type="hidden" name="SETTINGS['.$htmlID.'][SECRET_KEY]" id="'.$htmlID.'SECRET_KEY" value="'.htmlspecialcharsbx($arSettings['SECRET_KEY']).'"><input type="text" size="55" name="'.$htmlID.'INP_SECRET_KEY" id="'.$htmlID.'INP_SECRET_KEY" value="'.htmlspecialcharsbx($arSettings['SECRET_KEY']).'" autocomplete="off" '.($arBucket['READ_ONLY'] === 'Y'? '"disabled"': '').' onchange="BX(\''.$htmlID.'SECRET_KEY\').value = this.value"></td>
		</tr>
		<tr id="SETTINGS_2_'.$htmlID.'" style="display:'.($cur_SERVICE_ID === $this->GetID() || !$bServiceSet? '': 'none').'" class="settings-tr">
			<td>&nbsp;</td>
			<td>'.BeginNote().GetMessage("CLO_STORAGE_S3_EDIT_HELP").EndNote().'</td>
		</tr>
		';
		return $result;
	}
	/**
	 * @param array[string]string $arBucket
	 * @param array[string]string $arSettings
	 * @return bool
	*/
	function CheckSettings($arBucket, &$arSettings)
	{
		global $APPLICATION;
		$aMsg =/*.(array[int][string]string).*/array();

		$result = array(
			"ACCESS_KEY" => is_array($arSettings)? trim($arSettings["ACCESS_KEY"]): '',
			"SECRET_KEY" => is_array($arSettings)? trim($arSettings["SECRET_KEY"]): '',
		);
		if(is_array($arSettings) && array_key_exists("SESSION_TOKEN", $arSettings))
		{
			$result["SESSION_TOKEN"] = trim($arSettings["SESSION_TOKEN"]);
		}

		if($arBucket["READ_ONLY"] !== "Y" && $result["ACCESS_KEY"] === '')
		{
			$aMsg[] = array(
				"id" => $this->GetID()."INP_ACCESS_KEY",
				"text" => GetMessage("CLO_STORAGE_S3_EMPTY_ACCESS_KEY"),
			);
		}

		if($arBucket["READ_ONLY"] !== "Y" && $result["SECRET_KEY"] === '')
		{
			$aMsg[] = array(
				"id" => $this->GetID()."INP_SECRET_KEY",
				"text" => GetMessage("CLO_STORAGE_S3_EMPTY_SECRET_KEY"),
			);
		}

		if(!empty($aMsg))
		{
			$e = new CAdminException($aMsg);
			$APPLICATION->ThrowException($e);
			return false;
		}
		else
		{
			$arSettings = $result;
		}

		return true;
	}
	/**
	 * @param string $data
	 * @param string $key
	 * @return string
	*/
	function hmacsha1($data, $key)
	{
		if(strlen($key)>64)
			$key=pack('H*', sha1($key));
		$key = str_pad($key, 64, chr(0x00));
		$ipad = str_repeat(chr(0x36), 64);
		$opad = str_repeat(chr(0x5c), 64);
		$hmac = pack('H*', sha1(($key^$opad).pack('H*', sha1(($key^$ipad).$data))));
		return $hmac;
	}
	/**
	 * @param array[string]string $arSettings
	 * @param string $RequestMethod
	 * @param string $bucket
	 * @param string $RequestURI
	 * @param string $ContentType
	 * @param array[string]string $additional_headers
	 * @param string $params
	 * @param string|resource $content
	 * @return array[string]string
	*/
	function SignRequest($arSettings, $RequestMethod, $bucket, $RequestURI, $ContentType, $additional_headers, $params = "", $content = "")
	{
		static $search = array("+", "=");
		static $replace = array("%20", "%3D");

		$CanonicalizedResource = strlen($RequestURI)? str_replace($search, $replace, $RequestURI): "/";

		$CanonicalQuery = explode("&", ltrim($params, "?"));
		sort($CanonicalQuery);
		$CanonicalQueryString = implode("&", $CanonicalQuery);

		$CanonicalHeaders = array();
		foreach($additional_headers as $key => $value)
		{
			$key = strtolower($key);
			if (isset($CanonicalHeaders[$key]))
				$CanonicalHeaders[$key] .= ",";
			else
				$CanonicalHeaders[$key] = $key.":";
			$CanonicalHeaders[$key] .= trim($value, " \t\n\r");
		}
		ksort($CanonicalHeaders);
		$CanonicalHeadersString = implode("\n", $CanonicalHeaders);
		$SignedHeaders = implode(";", array_keys($CanonicalHeaders));

		if (is_resource($content))
		{
			$streamPosition = ftell($content);
			$hashResource = hash_init("sha256");
			hash_update_stream($hashResource, $content);
			$HashedPayload = hash_final($hashResource);
			fseek($content, $streamPosition);
		}
		else
		{
			$HashedPayload = hash("sha256", $content, false);
		}

		$CanonicalRequest = "";
		$CanonicalRequest .= $RequestMethod."\n";
		$CanonicalRequest .= $CanonicalizedResource."\n";
		$CanonicalRequest .= $CanonicalQueryString."\n";
		$CanonicalRequest .= $CanonicalHeadersString."\n\n";
		$CanonicalRequest .= $SignedHeaders."\n";
		$CanonicalRequest .= $HashedPayload;

		$Algorithm = "AWS4-HMAC-SHA256";
		$Time = time();
		$RequestDate = gmdate('Ymd', $Time);
		$RequestTime = gmdate('Ymd', $Time).'T'.gmdate('His', $Time).'Z';
		$Region = $this->location? $this->location: 'us-east-1';
		$Service = "s3";
		$Scope = $RequestDate."/".$Region."/".$Service."/aws4_request";

		$StringToSign = "";
		$StringToSign .= $Algorithm."\n";
		$StringToSign .= $RequestTime."\n";
		$StringToSign .= $Scope."\n";
		$StringToSign .= hash("sha256", $CanonicalRequest, false);

		$kSecret  = $arSettings["SECRET_KEY"];
		$kDate    = hash_hmac("sha256", $RequestDate, "AWS4".$kSecret, true);
		$kRegion  = hash_hmac("sha256", $Region, $kDate, true);
		$kService = hash_hmac("sha256", $Service, $kRegion, true);
		$kSigning = hash_hmac("sha256", "aws4_request", $kService, true);

		$Signature = hash_hmac("sha256", $StringToSign, $kSigning, false);

		$Authorization = "$Algorithm Credential=$arSettings[ACCESS_KEY]/$Scope, SignedHeaders=$SignedHeaders, Signature=$Signature";

		return array(
			"Date" => $RequestTime,
			"Authorization" => $Authorization,
			"x-amz-content-sha256" => $HashedPayload,
		);
	}
	/**
	 * @param string $location
	 * @return void
	 **/
	function SetLocation($location)
	{
		if ($location)
			$this->location = $location;
		else
			$this->location = "";
	}
	protected function GetRequestHost($bucket)
	{
		if(
			$this->new_end_point != ""
			&& preg_match('#^(http|https)://'.preg_quote($bucket, '#').'(.+?)/#', $this->new_end_point, $match) > 0
		)
		{
			return $bucket.$match[2];
		}
		elseif ($this->location)
		{
			return $bucket.".s3-".$this->location.".amazonaws.com";
		}
		else
		{
			return $bucket.".s3.amazonaws.com";
		}
	}
	/**
	 * @param array[string]string $arSettings
	 * @param string $verb
	 * @param string $bucket
	 * @param string $file_name
	 * @param string $params
	 * @param string $content
	 * @param array[string]string $additional_headers
	 * @return mixed
	*/
	function SendRequest($arSettings, $verb, $bucket, $file_name='/', $params='', $content='', $additional_headers=/*.(array[string]string).*/array())
	{
		global $APPLICATION;
		$this->status = 0;

		$obRequest = new CHTTP;
		if (isset($additional_headers["option-file-result"]))
		{
			$obRequest->fp = $additional_headers["option-file-result"];
		}

		if(isset($additional_headers["Content-Type"]))
		{
			$ContentType = $additional_headers["Content-Type"];
		}
		else
		{
			$ContentType = $content != ""? 'text/plain': '';
		}
		unset($additional_headers["Content-Type"]);

		foreach($this->set_headers as $key => $value)
		{
			$additional_headers[$key] = $value;
		}

		if(array_key_exists("SESSION_TOKEN", $arSettings))
		{
			$additional_headers["x-amz-security-token"] = $arSettings["SESSION_TOKEN"];
		}

		$additional_headers["host"] = $this->GetRequestHost($bucket);

		foreach($this->SignRequest($arSettings, $verb, $bucket, $file_name, $ContentType, $additional_headers, $params, $content) as $key => $value)
		{
			$obRequest->additional_headers[$key] = $value;
		}

		foreach($additional_headers as $key => $value)
			if(preg_match("/^(option-|host\$)/", $key) == 0)
				$obRequest->additional_headers[$key] = $value;

		$host = $additional_headers["host"];

		$was_end_point = $this->new_end_point;
		$this->new_end_point = '';

		$obRequest->Query($verb, $host, 80, $file_name.$params, $content, '', $ContentType);
		$this->status = $obRequest->status;
		$this->host = $host;
		$this->verb = $verb;
		$this->url =  $file_name.$params;
		$this->headers = $obRequest->headers;
		$this->errno = $obRequest->errno;
		$this->errstr = $obRequest->errstr;
		$this->result = $obRequest->result;

		if($obRequest->status == 200)
		{
			if(
				isset($additional_headers["option-raw-result"])
				|| isset($additional_headers["option--result"])
			)
			{
				return $obRequest->result;
			}
			elseif($obRequest->result != "")
			{
				$obXML = new CDataXML;
				$text = preg_replace("/<"."\\?XML.*?\\?".">/i", "", $obRequest->result);
				if($obXML->LoadString($text))
				{
					$arXML = $obXML->GetArray();
					if(is_array($arXML))
					{
						return $arXML;
					}
				}
				//XML parse error
				$e = new CApplicationException(GetMessage('CLO_STORAGE_S3_XML_PARSE_ERROR', array('#errno#'=>'1')));
				$APPLICATION->ThrowException($e);
				return false;
			}
			else
			{
				//Empty success result
				return array();
			}
		}
		elseif(
			$obRequest->status == 307  //Temporary redirect
			&& isset($obRequest->headers["Location"])
			&& $was_end_point === "" //No recurse yet
		)
		{
			$this->new_end_point = $obRequest->headers["Location"];
			return $this->SendRequest(
				$arSettings,
				$verb,
				$bucket,
				$file_name,
				$params,
				$content,
				$additional_headers
			);
		}
		elseif($obRequest->status > 0)
		{
			if($obRequest->result != "")
			{
				$obXML = new CDataXML;
				if($obXML->LoadString($obRequest->result))
				{
					$node = $obXML->SelectNodes("/Error/Message");
					if (is_object($node))
					{
						$errorMessage = trim($node->textContent(), '.');
						$e = new CApplicationException(GetMessage('CLO_STORAGE_S3_XML_ERROR', array(
							'#errmsg#' => $errorMessage,
						)));
						$APPLICATION->ThrowException($e);
						return false;
					}
				}
			}
			$e = new CApplicationException(GetMessage('CLO_STORAGE_S3_XML_PARSE_ERROR', array('#errno#'=>'2')));
			$APPLICATION->ThrowException($e);
			return false;
		}
		else
		{
			$e = new CApplicationException(GetMessage('CLO_STORAGE_S3_XML_PARSE_ERROR', array('#errno#'=>'3')));
			$APPLICATION->ThrowException($e);
			return false;
		}
	}
	/**
	 * @param array[string]string $arBucket
	 * @return bool
	*/
	function CreateBucket($arBucket)
	{
		global $APPLICATION;

		$arFiles = $this->ListFiles($arBucket, '/');
		if(is_array($arFiles))
			return true;

		if($arBucket["LOCATION"] != "")
			$content =
				'<CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">'.
				'<LocationConstraint>'.$arBucket["LOCATION"].'</LocationConstraint>'.
				'</CreateBucketConfiguration>';
		else
			$content = '';

		$this->SetLocation($arBucket["LOCATION"]);
		$response = $this->SendRequest(
			$arBucket["SETTINGS"],
			'PUT',
			$arBucket["BUCKET"],
			'/',
			'',
			$content
		);

		if($this->status == 409/*Already exists*/)
		{
			$APPLICATION->ResetException();
			return true;
		}
		else
		{
			return is_array($response);
		}
	}
	/**
	 * @param array[string]string $arBucket
	 * @return bool
	*/
	function DeleteBucket($arBucket)
	{
		global $APPLICATION;

		if($arBucket["PREFIX"] != "")
		{
			//Do not delete bucket if there is some files left
			if(!$this->IsEmptyBucket($arBucket))
				return false;

			//Let's pretend we deleted the bucket
			return true;
		}

		$this->SetLocation($arBucket["LOCATION"]);
		$response = $this->SendRequest(
			$arBucket["SETTINGS"],
			'DELETE',
			$arBucket["BUCKET"]
		);

		if(
			$this->status == 204/*No content*/
			|| $this->status == 404/*Not exists*/
			|| $this->status == 403/*Access denied*/
		)
		{
			$APPLICATION->ResetException();
			return true;
		}
		else
		{
			return is_array($response);
		}
	}
	/**
	 * @param array[string]string $arBucket
	 * @return bool
	*/
	function IsEmptyBucket($arBucket)
	{
		global $APPLICATION;

		$this->SetLocation($arBucket["LOCATION"]);
		$response = $this->SendRequest(
			$arBucket["SETTINGS"],
			'GET',
			$arBucket["BUCKET"],
			'/',
			'?max-keys=1'.($arBucket["PREFIX"] != ""? '&prefix='.$arBucket["PREFIX"].'/': '')
		);

		if($this->status == 404 || $this->status == 403)
		{
			$APPLICATION->ResetException();
			return true;
		}
		elseif(is_array($response))
		{
			return
				!isset($response["ListBucketResult"])
				|| !is_array($response["ListBucketResult"])
				|| !isset($response["ListBucketResult"]["#"])
				|| !is_array($response["ListBucketResult"]["#"])
				|| !isset($response["ListBucketResult"]["#"]["Contents"])
				|| !is_array($response["ListBucketResult"]["#"]["Contents"]);
		}
		else
		{
			return false;
		}
	}
	/**
	 * @param array[string]string $arBucket
	 * @param mixed $arFile
	 * @return string
	*/
	function GetFileSRC($arBucket, $arFile)
	{
		$proto = CMain::IsHTTPS()? "https": "http";

		static $aps = null;
		if (!$aps)
			$aps = self::GetAPList();

		if($arBucket["CNAME"] != "")
		{
			$host = $arBucket["CNAME"];
			$pref = "";
		}
		elseif ($proto === "https" && strpos($arBucket["BUCKET"], ".") !== false)
		{
			if (isset($aps[$arBucket["LOCATION"]]))
				$host = $aps[$arBucket["LOCATION"]];
			else
				$host = $aps[""];

			$pref = $arBucket["BUCKET"];
		}
		else
		{
			if (isset($aps[$arBucket["LOCATION"]]))
				$host = $arBucket["BUCKET"].".".$aps[$arBucket["LOCATION"]];
			else
				$host = $aps[""];

			$pref = "";
		}

		if(is_array($arFile))
			$URI = ltrim($arFile["SUBDIR"]."/".$arFile["FILE_NAME"], "/");
		else
			$URI = ltrim($arFile, "/");

		if ($arBucket["PREFIX"] != "")
		{
			if(substr($URI, 0, strlen($arBucket["PREFIX"])+1) !== $arBucket["PREFIX"]."/")
				$URI = $arBucket["PREFIX"]."/".$URI;
		}

		if ($pref !== "")
		{
			$URI = $pref."/".$URI;
		}

		return $proto."://$host/".CCloudUtil::URLEncode($URI, "UTF-8");
	}
	/**
	 * @param array[string]string $arBucket
	 * @param string $filePath
	 * @return bool
	*/
	function FileExists($arBucket, $filePath)
	{
		global $APPLICATION;

		if($arBucket["PREFIX"] != "")
		{
			if(substr($filePath, 0, strlen($arBucket["PREFIX"])+2) != "/".$arBucket["PREFIX"]."/")
				$filePath = "/".$arBucket["PREFIX"]."/".ltrim($filePath, "/");
		}
		$filePath = CCloudUtil::URLEncode($filePath, "UTF-8");

		$this->SetLocation($arBucket["LOCATION"]);
		$this->SendRequest(
			$arBucket["SETTINGS"],
			'HEAD',
			$arBucket["BUCKET"],
			$filePath
		);

		if($this->status == 200)
		{
			if (isset($this->headers["Content-Length"]) && $this->headers["Content-Length"] > 0)
				return $this->headers["Content-Length"];
			else
				return true;
		}
		elseif($this->status == 206)
		{
			$APPLICATION->ResetException();
			return true;
		}
		else//if($this->status == 404)
		{
			$APPLICATION->ResetException();
			return false;
		}
	}

	function FileCopy($arBucket, $arFile, $filePath)
	{
		global $APPLICATION;

		if($arBucket["PREFIX"])
		{
			if(substr($filePath, 0, strlen($arBucket["PREFIX"])+2) != "/".$arBucket["PREFIX"]."/")
				$filePath = "/".$arBucket["PREFIX"]."/".ltrim($filePath, "/");
		}

		$additional_headers = array();
		if($this->_public)
			$additional_headers["x-amz-acl"] = "public-read";
		$additional_headers["x-amz-copy-source"] = CCloudUtil::URLEncode("/".$arBucket["BUCKET"]."/".($arBucket["PREFIX"]? $arBucket["PREFIX"]."/": "").($arFile["SUBDIR"]? $arFile["SUBDIR"]."/": "").$arFile["FILE_NAME"], "UTF-8");
		$additional_headers["Content-Type"] = $arFile["CONTENT_TYPE"];

		$this->SetLocation($arBucket["LOCATION"]);
		$this->SendRequest(
			$arBucket["SETTINGS"],
			'PUT',
			$arBucket["BUCKET"],
			CCloudUtil::URLEncode($filePath, "UTF-8"),
			'',
			'',
			$additional_headers
		);

		if($this->status == 200)
		{
			return $this->GetFileSRC($arBucket, $filePath);
		}
		else//if($this->status == 404)
		{
			$APPLICATION->ResetException();
			return false;
		}
	}

	function DownloadToFile($arBucket, $arFile, $filePath)
	{
		$io = CBXVirtualIo::GetInstance();
		$obRequest = new CHTTP;
		$obRequest->follow_redirect = true;
		return $obRequest->Download($this->GetFileSRC($arBucket, $arFile), $io->GetPhysicalName($filePath));
	}

	function DeleteFile($arBucket, $filePath)
	{
		global $APPLICATION;

		if($arBucket["PREFIX"])
		{
			if(substr($filePath, 0, strlen($arBucket["PREFIX"])+2) != "/".$arBucket["PREFIX"]."/")
				$filePath = "/".$arBucket["PREFIX"]."/".ltrim($filePath, "/");
		}
		$filePath = CCloudUtil::URLEncode($filePath, "UTF-8");

		$this->SetLocation($arBucket["LOCATION"]);
		$this->SendRequest(
			$arBucket["SETTINGS"],
			'DELETE',
			$arBucket["BUCKET"],
			$filePath
		);

		if($this->status == 204)
		{
			$APPLICATION->ResetException();
			return true;
		}
		else//if($this->status == 404)
		{
			$APPLICATION->ResetException();
			return false;
		}
	}

	function SaveFile($arBucket, $filePath, $arFile)
	{
		global $APPLICATION;

		if($arBucket["PREFIX"])
		{
			if(substr($filePath, 0, strlen($arBucket["PREFIX"])+2) != "/".$arBucket["PREFIX"]."/")
				$filePath = "/".$arBucket["PREFIX"]."/".ltrim($filePath, "/");
		}
		$filePath = CCloudUtil::URLEncode($filePath, "UTF-8");

		$additional_headers = array();
		if($this->_public)
			$additional_headers["x-amz-acl"] = "public-read";
		$additional_headers["Content-Type"] = $arFile["type"];
		$additional_headers["Content-Length"] = (array_key_exists("content", $arFile)? CUtil::BinStrlen($arFile["content"]): filesize($arFile["tmp_name"]));

		$this->SetLocation($arBucket["LOCATION"]);
		$this->SendRequest(
			$arBucket["SETTINGS"],
			'PUT',
			$arBucket["BUCKET"],
			$filePath,
			'',
			(array_key_exists("content", $arFile)? $arFile["content"]: fopen($arFile["tmp_name"], "rb")),
			$additional_headers
		);

		if($this->status == 200)
		{
			return true;
		}
		elseif ($this->status == 400 && strpos($this->result, 'ExpiredToken') !== false)
		{
			$this->tokenHasExpired = true;
			return false;
		}
		elseif($this->status == 403)
		{
			AddMessage2Log($this);
			return false;
		}
		else
		{
			AddMessage2Log($this);
			$APPLICATION->ResetException();
			return false;
		}
	}

	function ListFiles($arBucket, $filePath, $bRecursive = false)
	{
		global $APPLICATION;

		$result = array(
			"dir" => array(),
			"file" => array(),
			"file_size" => array(),
		);

		$filePath = trim($filePath, '/');
		if(strlen($filePath))
			$filePath .= '/';

		if($arBucket["PREFIX"])
		{
			if(substr($filePath, 0, strlen($arBucket["PREFIX"])+2) != "/".$arBucket["PREFIX"]."/")
				$filePath = $arBucket["PREFIX"]."/".ltrim($filePath, "/");
		}
		$filePath = $APPLICATION->ConvertCharset($filePath, LANG_CHARSET, "UTF-8");

		$this->SetLocation($arBucket["LOCATION"]);
		$marker = '';
		while(true)
		{
			$response = $this->SendRequest(
				$arBucket["SETTINGS"],
				'GET',
				$arBucket["BUCKET"],
				'/',
				'?'.($bRecursive? '': 'delimiter=%2F&').'prefix='.urlencode($filePath)
					.'&marker='.str_replace("+", "%20", urlencode($marker))
			);

			if(
				$this->status == 200
				&& is_array($response)
				&& isset($response["ListBucketResult"])
				&& is_array($response["ListBucketResult"])
				&& isset($response["ListBucketResult"]["#"])
				&& is_array($response["ListBucketResult"]["#"])
			)
			{
				if(
					isset($response["ListBucketResult"]["#"]["CommonPrefixes"])
					&& is_array($response["ListBucketResult"]["#"]["CommonPrefixes"])
				)
				{
					foreach($response["ListBucketResult"]["#"]["CommonPrefixes"] as $a)
					{
						$dir_name = substr(rtrim($a["#"]["Prefix"][0]["#"], "/"), strlen($filePath));
						$result["dir"][] = $APPLICATION->ConvertCharset(urldecode($dir_name), "UTF-8", LANG_CHARSET);
					}
				}

				$lastKey = null;
				if(
					isset($response["ListBucketResult"]["#"]["Contents"])
					&& is_array($response["ListBucketResult"]["#"]["Contents"])
				)
				{
					foreach($response["ListBucketResult"]["#"]["Contents"] as $a)
					{
						$file_name = substr($a["#"]["Key"][0]["#"], strlen($filePath));
						$result["file"][] = $APPLICATION->ConvertCharset($file_name, "UTF-8", LANG_CHARSET);
						$result["file_size"][] = $a["#"]["Size"][0]["#"];
						$lastKey = $a["#"]["Key"][0]["#"];
					}
				}

				if(
					isset($response["ListBucketResult"]["#"]["IsTruncated"])
					&& is_array($response["ListBucketResult"]["#"]["IsTruncated"])
					&& $response["ListBucketResult"]["#"]["IsTruncated"][0]["#"] === "true"
				)
				{
					if (strlen($response["ListBucketResult"]["#"]["NextMarker"][0]["#"]) > 0)
					{
						$marker = $response["ListBucketResult"]["#"]["NextMarker"][0]["#"];
						continue;
					}
					elseif ($lastKey !== null);
					{
						$marker = $lastKey;
						continue;
					}
				}

				break;
			}
			elseif ($this->status == 400 && strpos($this->result, 'ExpiredToken') !== false)
			{
				$this->tokenHasExpired = true;
				return false;
			}
			else
			{
				return false;
			}
		}

		return $result;
	}

	function InitiateMultipartUpload($arBucket, &$NS, $filePath, $fileSize, $ContentType)
	{
		$filePath = '/'.trim($filePath, '/');
		if($arBucket["PREFIX"])
		{
			if(substr($filePath, 0, strlen($arBucket["PREFIX"])+2) != "/".$arBucket["PREFIX"]."/")
				$filePath = "/".$arBucket["PREFIX"].$filePath;
		}
		$filePathU = CCloudUtil::URLEncode($filePath, "UTF-8");

		$additional_headers = array();
		if($this->_public)
			$additional_headers["x-amz-acl"] = "public-read";
		$additional_headers["Content-Type"] = $ContentType;

		$this->SetLocation($arBucket["LOCATION"]);
		$response = $this->SendRequest(
			$arBucket["SETTINGS"],
			'POST',
			$arBucket["BUCKET"],
			$filePathU,
			'?uploads=',
			'',
			$additional_headers
		);

		if(
			$this->status == 200
			&& is_array($response)
			&& isset($response["InitiateMultipartUploadResult"])
			&& is_array($response["InitiateMultipartUploadResult"])
			&& isset($response["InitiateMultipartUploadResult"]["#"])
			&& is_array($response["InitiateMultipartUploadResult"]["#"])
			&& isset($response["InitiateMultipartUploadResult"]["#"]["UploadId"])
			&& is_array($response["InitiateMultipartUploadResult"]["#"]["UploadId"])
			&& isset($response["InitiateMultipartUploadResult"]["#"]["UploadId"][0])
			&& is_array($response["InitiateMultipartUploadResult"]["#"]["UploadId"][0])
			&& isset($response["InitiateMultipartUploadResult"]["#"]["UploadId"][0]["#"])
			&& is_string($response["InitiateMultipartUploadResult"]["#"]["UploadId"][0]["#"])
		)
		{
			$NS = array(
				"filePath" => $filePath,
				"UploadId" => $response["InitiateMultipartUploadResult"]["#"]["UploadId"][0]["#"],
				"Parts" => array(),
			);
			return true;
		}
		else
		{
			return false;
		}
	}

	function GetMinUploadPartSize()
	{
		return BX_S3_MIN_UPLOAD_PART_SIZE;
	}

	function UploadPartNo($arBucket, &$NS, $data, $part_no)
	{
		$filePath = '/'.trim($NS["filePath"], '/');
		if($arBucket["PREFIX"])
		{
			if(substr($filePath, 0, strlen($arBucket["PREFIX"])+2) != "/".$arBucket["PREFIX"]."/")
				$filePath = "/".$arBucket["PREFIX"].$filePath;
		}
		$filePath = CCloudUtil::URLEncode($filePath, "UTF-8");

		$this->SetLocation($arBucket["LOCATION"]);
		$this->SendRequest(
			$arBucket["SETTINGS"],
			'PUT',
			$arBucket["BUCKET"],
			$filePath,
			'?partNumber='.($part_no + 1).'&uploadId='.urlencode($NS["UploadId"]),
			$data
		);

		if($this->status == 200 && is_array($this->headers))
		{
			foreach ($this->headers as $key => $value)
			{
				if (strtolower($key) === "etag")
				{
					$NS["Parts"][$part_no] = $value;
					return true;
				}
			}
		}
		return false;
	}

	function UploadPart($arBucket, &$NS, $data)
	{
		return $this->UploadPartNo($arBucket, $NS, $data, count($NS["Parts"]));
	}

	function CompleteMultipartUpload($arBucket, &$NS)
	{
		$filePath = '/'.trim($NS["filePath"], '/');
		if($arBucket["PREFIX"])
		{
			if(substr($filePath, 0, strlen($arBucket["PREFIX"])+2) != "/".$arBucket["PREFIX"]."/")
				$filePath = "/".$arBucket["PREFIX"].$filePath;
		}
		$filePath = CCloudUtil::URLEncode($filePath, "UTF-8");

		ksort($NS["Parts"]);
		$data = "";
		foreach($NS["Parts"] as $PartNumber => $ETag)
		{
			$data .= "<Part><PartNumber>".($PartNumber+1)."</PartNumber><ETag>".$ETag."</ETag></Part>\n";
		}

		$this->SetLocation($arBucket["LOCATION"]);
		$this->SendRequest(
			$arBucket["SETTINGS"],
			'POST',
			$arBucket["BUCKET"],
			$filePath,
			'?uploadId='.urlencode($NS["UploadId"]),
			"<CompleteMultipartUpload>".$data."</CompleteMultipartUpload>"
		);

		return $this->status == 200;
	}

	function setPublic($state = true)
	{
		$this->_public = $state !== false;
	}

	function setHeader($key, $value)
	{
		$this->set_headers[$key] = $value;
	}

	function unsetHeader($key)
	{
		unset($this->set_headers[$key]);
	}

	function getHeaders()
	{
		return $this->headers;
	}
}

Zerion Mini Shell 1.0