archive-ccc8458/video/api/chunked/put.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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<?php
declare(strict_types=1);
header('Content-Type: application/json');
require_once __DIR__ . '/../../lib/auth.php';
require_once __DIR__ . '/../../lib/chunked.php';
require_https();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'method not allowed']);
exit;
}
if (!rate_limit_check()) {
http_response_code(429);
echo json_encode(['error' => 'too many failed attempts, try again later']);
exit;
}
$pw = password_from_request();
if ($pw === null || $pw === '' || !verify_password($pw)) {
rate_limit_fail();
http_response_code(401);
echo json_encode(['error' => 'invalid password']);
exit;
}
rate_limit_reset();
$id = (string)($_GET['id'] ?? '');
$index = (int)($_GET['index'] ?? -1);
if (!is_valid_upload_id($id)) {
http_response_code(400);
echo json_encode(['error' => 'invalid upload id']);
exit;
}
$meta = read_meta($id);
if ($meta === null) {
http_response_code(404);
echo json_encode(['error' => 'unknown upload id']);
exit;
}
$expected = (int) $meta['expected_chunks'];
if ($index < 0 || $index >= $expected) {
http_response_code(400);
echo json_encode(['error' => 'invalid chunk index', 'expected_chunks' => $expected]);
exit;
}
$dest = chunk_path($id, $index);
if ($dest === null) {
http_response_code(400);
echo json_encode(['error' => 'invalid chunk index']);
exit;
}
// Stream php://input straight to the chunk file so memory usage stays low.
$in = fopen('php://input', 'rb');
if ($in === false) {
http_response_code(400);
echo json_encode(['error' => 'could not read body']);
exit;
}
$out = fopen($dest, 'wb');
if ($out === false) {
fclose($in);
http_response_code(500);
echo json_encode(['error' => 'could not open chunk file']);
exit;
}
$written = 0;
while (!feof($in)) {
$buf = fread($in, 65536);
if ($buf === false || $buf === '') break;
$n = fwrite($out, $buf);
if ($n === false) {
fclose($in); fclose($out);
@unlink($dest);
http_response_code(500);
echo json_encode(['error' => 'write failed']);
exit;
}
$written += $n;
if ($written > MAX_CHUNK_BYTES) {
fclose($in); fclose($out);
@unlink($dest);
http_response_code(413);
echo json_encode(['error' => 'chunk too large', 'max' => MAX_CHUNK_BYTES]);
exit;
}
}
fclose($in);
fclose($out);
@chmod($dest, 0600);
if ($written === 0) {
@unlink($dest);
http_response_code(400);
echo json_encode(['error' => 'empty chunk']);
exit;
}
// Last chunk may be smaller; middle chunks must equal chunk_size.
$isLast = $index === $expected - 1;
$expectedChunkSize = (int) $meta['chunk_size'];
if (!$isLast && $written !== $expectedChunkSize) {
@unlink($dest);
http_response_code(400);
echo json_encode([
'error' => 'chunk size mismatch',
'expected' => $expectedChunkSize,
'got' => $written,
]);
exit;
}
echo json_encode([
'upload_id' => $id,
'index' => $index,
'size' => $written,
'received' => count(chunked_uploaded_indices($id, $expected)),
'expected' => $expected,
]);