55 lines
5.1 KiB
PHP
55 lines
5.1 KiB
PHP
<?php
|
|
// Disposable loopback-only upstream API check. Not Archipelago runtime acceptance.
|
|
function request($method, $path, $user='alice', $password='Synthetic-Cloud-A-Only-2026!', $headers=[], $body=null) {
|
|
$curl = curl_init('http://127.0.0.1'.$path);
|
|
$seen = [];
|
|
curl_setopt_array($curl, [CURLOPT_CUSTOMREQUEST=>$method, CURLOPT_RETURNTRANSFER=>true,
|
|
CURLOPT_FOLLOWLOCATION=>false, CURLOPT_TIMEOUT=>15, CURLOPT_PROXY=>'',
|
|
CURLOPT_USERPWD=>$user.':'.$password, CURLOPT_HTTPHEADER=>$headers,
|
|
CURLOPT_HEADERFUNCTION=>function($c,$line) use (&$seen) { $parts=explode(':',$line,2); if(count($parts)===2) $seen[strtolower(trim($parts[0]))]=trim($parts[1]); return strlen($line); }]);
|
|
if($method==='HEAD') curl_setopt($curl, CURLOPT_NOBODY, true);
|
|
if($body!==null) curl_setopt($curl,CURLOPT_POSTFIELDS,$body);
|
|
$result=curl_exec($curl); $status=curl_getinfo($curl,CURLINFO_RESPONSE_CODE);
|
|
if($result===false) throw new Exception('Loopback request failed');
|
|
curl_close($curl); return [$status,$seen,$result];
|
|
}
|
|
function check($condition,$name) { if(!$condition) throw new Exception('FAIL '.$name); echo 'PASS '.$name."\n"; }
|
|
if (($argv[1]??'')==='revoked') {
|
|
[$status,,$body]=request('GET','/remote.php/dav/files/alice/contract.txt');
|
|
echo 'Disabled-account HTTP status '.$status.'; exact file bytes returned: '.($body==='abcdefgh'?'yes':'no')."\n";
|
|
$disabledStatus=$status; $disabledBody=$body;
|
|
echo 'Disabled-account response: '.substr(trim(strip_tags($body)),0,300)."\n";
|
|
[$status,,$body]=request('GET','/ocs/v2.php/cloud/user?format=json','bob','Synthetic-Cloud-B-Only-2026!',['OCS-APIRequest: true','Accept: application/json']);
|
|
check($status===200 && json_decode($body,true)['ocs']['data']['id']==='bob','other account survives source-account revocation');
|
|
check(in_array($disabledStatus,[401,403,503],true) && $disabledBody!=='abcdefgh' && str_contains(strtolower($disabledBody),'disabled'),'disabled account loses authenticated file access with explicit disabled-account error');
|
|
exit(0);
|
|
}
|
|
[$status,,$body]=request('GET','/status.php'); $version=json_decode($body,true);
|
|
check($status===200 && $version['installed']===true && $version['maintenance']===false && str_starts_with($version['version'],'29.'),'pinned Nextcloud29 readiness');
|
|
echo 'Version '.$version['version']."\n";
|
|
[$status,,$body]=request('GET','/ocs/v2.php/cloud/user?format=json','alice','Synthetic-Cloud-A-Only-2026!',['OCS-APIRequest: true','Accept: application/json']);
|
|
check($status===200 && json_decode($body,true)['ocs']['data']['id']==='alice','authenticated OCS identity');
|
|
[$status]=request('PUT','/remote.php/dav/files/alice/contract.txt','alice','Synthetic-Cloud-A-Only-2026!',['Content-Type: text/plain'],'abcdefgh');
|
|
check($status===201 || $status===204,'disposable account file creation');
|
|
$xml='<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns"><d:prop><oc:fileid/><oc:permissions/><d:getetag/><d:resourcetype/><d:getcontenttype/><d:getcontentlength/></d:prop></d:propfind>';
|
|
[$status,,$body]=request('PROPFIND','/remote.php/dav/files/alice/','alice','Synthetic-Cloud-A-Only-2026!',['Depth: 1','Content-Type: application/xml; charset=utf-8'],$xml);
|
|
$document=new DOMDocument(); check($status===207 && $document->loadXML($body,LIBXML_NONET),'Depth1 DAV XML');
|
|
$xpath=new DOMXPath($document); $xpath->registerNamespace('d','DAV:'); $xpath->registerNamespace('oc','http://owncloud.org/ns');
|
|
$rows=$xpath->query('//d:response[d:href="/remote.php/dav/files/alice/contract.txt"]');
|
|
check($rows->length===1,'account-relative unique href'); $row=$rows->item(0);
|
|
$etag=$xpath->evaluate('string(d:propstat[d:status="HTTP/1.1 200 OK"]/d:prop/d:getetag)',$row);
|
|
check($etag!=='' && str_contains($xpath->evaluate('string(d:propstat/d:prop/oc:permissions)',$row),'G'),'successful DAV etag/read permission');
|
|
[$status,$headers]=request('HEAD','/remote.php/dav/files/alice/contract.txt');
|
|
check($status===200 && ($headers['etag']??null)===$etag && ($headers['content-length']??null)==='8','HEAD matches DAV revision and exact length');
|
|
[$status,$headers,$body]=request('GET','/remote.php/dav/files/alice/contract.txt','alice','Synthetic-Cloud-A-Only-2026!',['If-Match: '.$etag,'Range: bytes=2-5','Accept-Encoding: identity']);
|
|
check($status===206 && ($headers['content-range']??null)==='bytes 2-5/8' && $body==='cdef','conditional exact range');
|
|
[$status,,$body]=request('GET','/remote.php/dav/files/alice/contract.txt','alice','Synthetic-Cloud-A-Only-2026!',['If-Match: '.$etag]);
|
|
check($status===200 && $body==='abcdefgh','authenticated exact full bytes');
|
|
[$status]=request('GET','/remote.php/dav/files/alice/contract.txt','bob','Synthetic-Cloud-B-Only-2026!');
|
|
check(in_array($status,[403,404],true),'other account cannot read private file');
|
|
[$status]=request('GET','/remote.php/dav/files/alice/contract.txt','alice','wrong-synthetic-password');
|
|
check($status===401,'invalid credentials refused');
|
|
[$status]=request('GET','/remote.php/dav/files/alice/contract.txt','alice','Synthetic-Cloud-A-Only-2026!',['If-Match: "changed-revision"']);
|
|
check($status===412,'changed revision refused');
|
|
echo "No File Browser, host port, external network or real account was used.\n";
|