archive-ccc8458/video/lib/chunked.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?php
declare(strict_types=1);
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/hash.php';
// Outside the videos dir; blocked from web access by routes in .htaccess
// plus a deny .htaccess we drop into the directory at creation time.
const UPLOADS_DIR = __DIR__ . '/../.uploads';
function ensure_uploads_dir(): void {
if (!is_dir(UPLOADS_DIR)) {
@mkdir(UPLOADS_DIR, 0700, true);
}
$deny = UPLOADS_DIR . '/.htaccess';
if (!is_file($deny)) {
@file_put_contents($deny, "Require all denied\nOptions -Indexes\n");
}
}
function is_valid_upload_id(string $id): bool {
return (bool) preg_match('/^[a-f0-9]{32}$/', $id);
}
function upload_dir(string $id): ?string {
if (!is_valid_upload_id($id)) return null;
return UPLOADS_DIR . '/' . $id;
}
function read_meta(string $id): ?array {
$dir = upload_dir($id);
if ($dir === null) return null;
$f = $dir . '/meta.json';
if (!is_file($f)) return null;
$j = json_decode((string) file_get_contents($f), true);
return is_array($j) ? $j : null;
}
function write_meta(string $id, array $meta): bool {
$dir = upload_dir($id);
if ($dir === null) return false;
if (!is_dir($dir)) @mkdir($dir, 0700, true);
$bytes = file_put_contents($dir . '/meta.json', json_encode($meta), LOCK_EX);
return $bytes !== false;
}
function chunk_path(string $id, int $index): ?string {
$dir = upload_dir($id);
if ($dir === null) return null;
if ($index < 0 || $index >= MAX_CHUNKS) return null;
return $dir . '/chunk-' . str_pad((string) $index, 5, '0', STR_PAD_LEFT) . '.bin';
}
function delete_upload_dir(string $id): void {
$dir = upload_dir($id);
if ($dir === null || !is_dir($dir)) return;
foreach (glob($dir . '/*') ?: [] as $f) @unlink($f);
@rmdir($dir);
}
function gc_orphan_uploads(): void {
if (!is_dir(UPLOADS_DIR)) return;
$cutoff = time() - UPLOAD_TTL_SEC;
foreach (scandir(UPLOADS_DIR) ?: [] as $entry) {
if ($entry === '.' || $entry === '..' || !is_valid_upload_id($entry)) continue;
$dir = UPLOADS_DIR . '/' . $entry;
if (!is_dir($dir)) continue;
$meta = $dir . '/meta.json';
$mtime = is_file($meta) ? filemtime($meta) : filemtime($dir);
if ($mtime !== false && $mtime < $cutoff) {
foreach (glob($dir . '/*') ?: [] as $f) @unlink($f);
@rmdir($dir);
}
}
}
function chunked_uploaded_indices(string $id, int $expected): array {
$present = [];
for ($i = 0; $i < $expected; $i++) {
$p = chunk_path($id, $i);
if ($p !== null && is_file($p)) $present[] = $i;
}
return $present;
}