$scheme,'host'=>$host,'port'=>$port]; } function hash_sha256_hex(string $d): string { return hash('sha256',$d); } function hmac_sha256_bin(string $key,string $msg): string { return hash_hmac('sha256',$msg,$key,true); } function getSignatureKey(string $k,string $date,string $region,string $service): string { $kDate=hmac_sha256_bin("AWS4".$k,$date); $kRegion=hmac_sha256_bin($kDate,$region); $kService=hmac_sha256_bin($kRegion,$service); return hmac_sha256_bin($kService,"aws4_request"); } /* Send signed HTTP request */ function s3_send_request(array $cfg,string $method,string $bucket,string $object='',array $qs=[], array $hdr=[], $body=null): array { $ep=s3_parse_endpoint($cfg['endpoint']); $region=$cfg['region']; $access=$cfg['access']; $secret=$cfg['secret']; $service='s3'; $uri = '/'; if($bucket!=='') { $uri .= $bucket; if($object!==''){ $uri .= '/' . ltrim($object, '/'); } } $now=new DateTime('UTC'); $amz=$now->format('Ymd\THis\Z'); $date=$now->format('Ymd'); if($body===null)$payloadHash=hash_sha256_hex(''); elseif(is_resource($body)){ $data=stream_get_contents($body); rewind($body); $payloadHash=hash_sha256_hex($data); } else $payloadHash=hash_sha256_hex((string)$body); $host=$ep['host']; if(($ep['scheme']==='https' && $ep['port']!=443)||($ep['scheme']==='http' && $ep['port']!=80)) $host.=':'.$ep['port']; $headers=['Host'=>$host,'x-amz-date'=>$amz,'x-amz-content-sha256'=>$payloadHash]+$hdr; ksort($headers); $canHdr=''; $signed=[]; foreach($headers as $k=>$v){$lk=strtolower($k); $canHdr.="$lk:$v\n"; $signed[]=$lk;} $signedStr=implode(';',$signed); ksort($qs); $qstr=http_build_query($qs,'','&',PHP_QUERY_RFC3986); $canonical="$method\n$uri\n$qstr\n$canHdr\n$signedStr\n$payloadHash"; $scope="$date/$region/$service/aws4_request"; $stringToSign="AWS4-HMAC-SHA256\n$amz\n$scope\n".hash_sha256_hex($canonical); $sign=hash_hmac('sha256',$stringToSign,getSignatureKey($secret,$date,$region,$service)); $headers['Authorization']="AWS4-HMAC-SHA256 Credential=$access/$scope, SignedHeaders=$signedStr, Signature=$sign"; $url=$ep['scheme'].'://'.$host.$uri.(empty($qstr)?'':'?'.$qstr); $ch=curl_init($url); curl_setopt_array($ch,[ CURLOPT_RETURNTRANSFER=>true, CURLOPT_HEADER=>true, CURLOPT_CUSTOMREQUEST=>$method, CURLOPT_HTTPHEADER=>array_map(fn($k,$v)=>"$k: $v",array_keys($headers),$headers), CURLOPT_NOBODY=>($method==='HEAD'), CURLOPT_FOLLOWLOCATION=>false, ]); if(in_array($method,['PUT','POST'])&&$body!==null){ $data=is_resource($body)?stream_get_contents($body):(string)$body; curl_setopt($ch,CURLOPT_POSTFIELDS,$data); } $resp=curl_exec($ch); if($resp===false)return['ok'=>false,'http_code'=>0,'error'=>curl_error($ch)]; $hs=curl_getinfo($ch,CURLINFO_HEADER_SIZE); $code=curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch); $header=substr($resp,0,$hs); $respBody=substr($resp,$hs); $h=[]; foreach(preg_split('/\r\n/',trim($header)) as $l){ if (strpos($l,':')!==false){ [$n,$v]=explode(':',$l,2); $h[strtolower(trim($n))]=trim($v); } } return ['ok'=>$code>=200&&$code<300,'http_code'=>$code,'headers'=>$h,'body'=>$respBody]; } /* Simple helpers */ function s3_list(array $cfg, string $bucket, string $prefix='', string $delim='/', int $max=1000, string $token=''): array { $q=['list-type'=>2,'delimiter'=>$delim,'max-keys'=>$max]; if($prefix!=='') $q['prefix']=s3_norm_prefix($prefix); if($token!=='') $q['continuation-token']=$token; return s3_send_request($cfg,'GET',$bucket,'',$q,[],null); } function s3_delete_prefix(array $cfg, string $bucket, string $prefix): array { // demo: iterate + DELETE từng object (đơn giản, an toàn) $deleted=0; $token=''; do{ $r=s3_list($cfg,$bucket,$prefix,'/',1000,$token); if(!$r['ok']) return ['ok'=>false,'http_code'=>$r['http_code'],'deleted'=>$deleted,'error'=>$r['body']]; $xml=@simplexml_load_string($r['body']); if(!$xml) break; $batch=[]; foreach($xml->Contents as $c) $batch[]=(string)$c->Key; foreach($batch as $k){ $d=s3_send_request($cfg,'DELETE',$bucket,$k,[],[],null); if($d['ok']) $deleted++; } $token=(string)($xml->NextContinuationToken ?? ''); } while(!empty($token)); return ['ok'=>true,'deleted'=>$deleted]; }