Source: s3helper.php
⬇️ Tải file Trang chính
<?php
// s3helper.php — AWS Signature V4 for MinIO demo (final+paths)
declare(strict_types=1);
if (session_status() === PHP_SESSION_NONE) session_start();

/* Load or update config (session only) */
function s3_load_config(): array {
    $cfg = $_SESSION['s3_demo'] ?? [];
    foreach (['endpoint','region','access','secret','bucket'] as $k)
        if (isset($_POST[$k])) $cfg[$k] = trim((string)$_POST[$k]);

    $cfg['endpoint'] = $cfg['endpoint'] ?? 'https://minio1.webtui.vn:9000';
    $cfg['region']   = $cfg['region'] ?? 'vn-1';
    if (empty($cfg['bucket'])) {
        $u = preg_replace('/[^a-z0-9]/i','', $cfg['access'] ?? 'demo');
        $cfg['bucket'] = 'bucket-' . ($u ?: 'demo');
    }
    $_SESSION['s3_demo'] = $cfg;
    return $cfg;
}

/* Path helpers */
function s3_norm_prefix(string $p): string {
    $p = trim($p);
    $p = ltrim($p, '/');                // không dẫn slash đầu
    $p = preg_replace('#/{2,}#','/',$p);// gom //
    return ($p === '' ? '' : rtrim($p,'/').'/'); // prefix luôn kết thúc '/'
}
function s3_join(string $prefix, string $name): string {
    $prefix = s3_norm_prefix($prefix);
    $name = ltrim($name, '/');
    return $prefix . $name;
}

/* SigV4 core */
function s3_parse_endpoint(string $endpoint): array {
    $p = parse_url($endpoint);
    $scheme = $p['scheme'] ?? 'https';
    $host   = $p['host']   ?? 'minio1.webtui.vn';
    $port   = $p['port']   ?? ($scheme === 'https' ? 443 : 80);
    return ['scheme'=>$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];
}