main • app/controllers/CommitController.php
<?php
class CommitController
{
private array $cfg;
public function __construct(array $cfg){ $this->cfg=$cfg; }
public function showZip(int $repoId): void {
$db = db();
$uid = require_auth();
if (!repo_can_write($db, $repoId, $uid)) { http_response_code(403); exit("403"); }
$repo = repo_get($db, $repoId);
$branch = safe_branch($_GET['branch'] ?? $repo['default_branch']);
$st = $db->prepare("SELECT name FROM branches WHERE repo_id=? ORDER BY name ASC");
$st->execute([$repoId]);
$branches = $st->fetchAll();
$title = $repo['owner_username'] . "/" . $repo['name'] . " / Commit ZIP";
require __DIR__ . '/../views/repos/commit_zip.php';
}
public function commitZip(int $repoId): void {
$db = db();
$uid = require_auth();
csrf_verify();
if (!repo_can_write($db, $repoId, $uid)) { http_response_code(403); exit("403"); }
$repo = repo_get($db, $repoId);
$branch = safe_branch($_POST['branch'] ?? $repo['default_branch']);
$message = trim($_POST['message'] ?? '');
if ($message === '' || strlen($message) > 255) {
flash_set('err', 'Commit message required (max 255).');
redirect("/r/$repoId/commit-zip?branch=$branch");
}
if (!isset($_FILES['zip']) || $_FILES['zip']['error'] !== UPLOAD_ERR_OK) {
flash_set('err', 'ZIP upload failed.');
redirect("/r/$repoId/commit-zip?branch=$branch");
}
$maxBytes = 10 * 1024 * 1024;
if ($_FILES['zip']['size'] > $maxBytes) {
flash_set('err', 'ZIP too large (max 10MB).');
redirect("/r/$repoId/commit-zip?branch=$branch");
}
$zipPath = $_FILES['zip']['tmp_name'];
$blobs = new BlobStore($this->cfg['storage_base']);
$engine = new RepoEngine($blobs);
$parent = $engine->getBranchHead($db, $repoId, $branch);
$changes = $this->zipToChanges($zipPath);
if (!$changes) {
flash_set('err', 'ZIP had no valid files.');
redirect("/r/$repoId/commit-zip?branch=$branch");
}
$commitId = $engine->commit($db, $repoId, $branch, $parent, $uid, $message, $changes);
flash_set('ok', "Committed ZIP (#$commitId).");
redirect("/r/$repoId/browse?branch=$branch");
}
private function zipToChanges(string $zipPath): array {
$z = new ZipArchive();
if ($z->open($zipPath) !== true) throw new RuntimeException("Invalid ZIP");
$changes = [];
for ($i=0; $i<$z->numFiles; $i++) {
$stat = $z->statIndex($i);
if (!$stat) continue;
$name = $stat['name'] ?? '';
$name = str_replace('\\', '/', $name);
if (str_ends_with($name, '/')) continue;
$name = ltrim($name, '/');
if ($name === '' || str_contains($name, '..') || str_contains($name, "\0")) continue;
if (strlen($name) > 700) continue;
$content = $z->getFromIndex($i);
if ($content === false) continue;
if (strlen($content) > 2 * 1024 * 1024) continue; // 2MB per file
// Normalize path validation
$name = safe_path($name);
$changes[] = ['path' => $name, 'content' => $content, 'delete' => false];
}
$z->close();
return $changes;
}
}