refactor: move Restrictions d'accès aux fichiers from contenus.php to acces.php, cleanup section

This commit is contained in:
Pontoporeia
2026-05-11 11:40:50 +02:00
parent d000f9e1d4
commit 1b0451581d
7 changed files with 152 additions and 67 deletions

View File

@@ -17,10 +17,11 @@
* instance's /api/v1/oauth-clients/local endpoint and cached in-memory
* per process lifetime.
*
* Upload uses the resumable protocol:
* POST /api/v1/videos/upload-resumable — init
* PUT /api/v1/videos/upload-resumable — send chunk
* DELETE /api/v1/videos/upload-resumable — cancel
* Upload uses the Google-resumable protocol:
* POST /api/v1/videos/upload-resumable — init (→ Location header with upload URL token)
* PATCH <Location URL> — send chunk
* HEAD <Location URL> — resume check
* DELETE <Location URL> — cancel
*/
class PeerTubeService
{
@@ -205,33 +206,31 @@ class PeerTubeService
'Content-Length: ' . strlen($initBody),
]);
$initJson = json_decode($initResponse['body'], true);
if ($initResponse['status'] < 200 || $initResponse['status'] >= 300) {
// PeerTube Google-resumable returns the upload session URL in the Location header.
// The JSON body contains video.id (upload session ID, not final video ID).
$chunkUrl = $initResponse['headers']['location'] ?? $initResponse['headers']['Location'] ?? null;
if (!$chunkUrl) {
$initJson = json_decode($initResponse['body'], true);
$msg = $initJson['error'] ?? $initJson['detail'] ?? $initResponse['body'];
throw new \RuntimeException('PeerTube upload init failed (' . $initResponse['status'] . '): ' . $msg);
throw new \RuntimeException('PeerTube upload init: no Location header (' . $initResponse['status'] . '): ' . $msg);
}
// Small files may complete in one shot
$shortUuid = $initJson['video']['shortUUID'] ?? $initJson['video']['uuid'] ?? null;
if ($shortUuid && !isset($initJson['video']['id'])) {
$watchUrl = rtrim($baseUrl, '/') . '/videos/watch/' . $shortUuid;
return ['uuid' => $shortUuid, 'watchUrl' => $watchUrl];
// Relative Location? Make it absolute.
if (!str_starts_with($chunkUrl, 'http')) {
$chunkUrl = rtrim($baseUrl, '/') . $chunkUrl;
}
$uploadId = $initJson['video']['id'] ?? null;
if (!$uploadId) {
throw new \RuntimeException('PeerTube upload init: no upload session returned.');
}
// ── Step 2: Send chunks ──
$chunkUrl = $baseUrl . '/api/v1/videos/upload-resumable?upload_id=' . urlencode((string)$uploadId);
// ── Step 2: Send chunks via PATCH (Google-resumable variant) ──
$fh = fopen($filePath, 'rb');
if (!$fh) {
throw new \RuntimeException('Cannot open file for resumable upload.');
}
$chunkSize = 4 * 1024 * 1024;
// Chunk size: 1 MB, must be a multiple of 256 KB (262144 bytes).
$chunkSizeBase = 256 * 1024;
$chunkSize = max($chunkSizeBase, min(4 * 1024 * 1024, (int)ceil($fileSize / 100)));
$chunkSize = (int)ceil($chunkSize / $chunkSizeBase) * $chunkSizeBase;
$offset = 0;
$lastResponse = null;
@@ -240,9 +239,9 @@ class PeerTubeService
$chunkLen = strlen($chunk);
$end = $offset + $chunkLen - 1;
$resp = self::httpRequest($chunkUrl, 'PUT', $chunk, [
$resp = self::httpRequest($chunkUrl, 'PATCH', $chunk, [
'Authorization: Bearer ' . $token,
'Content-Type: application/octet-stream',
'Content-Type: ' . $mimeType,
'Content-Range: bytes ' . $offset . '-' . $end . '/' . $fileSize,
'Content-Length: ' . $chunkLen,
], 600);
@@ -256,13 +255,12 @@ class PeerTubeService
break;
}
} elseif ($resp['status'] === 308) {
// Resume Incomplete — chunk accepted, continue
continue;
} else {
fclose($fh);
try {
self::httpRequest($chunkUrl, 'DELETE', '', [
'Authorization: Bearer ' . $token, 'Content-Length: 0',
], 10);
self::cancelUpload($chunkUrl, $token);
} catch (\Throwable $e) { /* ignore */ }
$errJson = json_decode($resp['body'], true);
$msg = $errJson['error'] ?? $errJson['detail'] ?? $resp['body'];
@@ -436,10 +434,25 @@ class PeerTubeService
// HTTP helper
// -------------------------------------------------------------------------
/**
* Cancel a resumable upload session.
*/
private static function cancelUpload(string $chunkUrl, string $token): void
{
self::httpRequest($chunkUrl, 'DELETE', '', [
'Authorization: Bearer ' . $token,
'Content-Length: 0',
], 10);
}
// -------------------------------------------------------------------------
// HTTP helper
// -------------------------------------------------------------------------
/**
* Minimal cURL HTTP helper.
*
* @return array{status:int, body:string}
* @return array{status:int, body:string, headers:array<string,string>}
*/
public static function httpRequest(
string $url,
@@ -457,31 +470,47 @@ class PeerTubeService
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => 15,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_FOLLOWLOCATION => false, // Must be false to capture Location header
CURLOPT_MAXREDIRS => 3,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_HEADERFUNCTION => function ($ch, $headerLine) use (&$responseHeaders) {
$len = strlen($headerLine);
$parts = explode(':', $headerLine, 2);
if (count($parts) === 2) {
$responseHeaders[strtolower(trim($parts[0]))] = trim($parts[1]);
}
return $len;
},
]);
$responseHeaders = [];
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
} elseif ($method === 'PUT') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
} elseif ($method === 'PATCH') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
} elseif ($method === 'DELETE') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
} elseif ($method === 'HEAD') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'HEAD');
curl_setopt($ch, CURLOPT_NOBODY, true);
}
$responseBody = curl_exec($ch);
$status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
if ($responseBody === false) {
if ($responseBody === false && $method !== 'HEAD') {
throw new \RuntimeException('Erreur réseau PeerTube : ' . $error);
}
return ['status' => $status, 'body' => (string)$responseBody];
return ['status' => $status, 'body' => (string)$responseBody, 'headers' => $responseHeaders];
}
}